diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
index 7bc2234..ee6c9f3 100644
--- a/src/pages/NotFound.tsx
+++ b/src/pages/NotFound.tsx
@@ -1,4 +1,4 @@
-import { useLocation } from "react-router-dom";
+import { Link, useLocation } from "react-router-dom";
import { useEffect } from "react";
const NotFound = () => {
@@ -9,13 +9,13 @@ const NotFound = () => {
}, [location.pathname]);
return (
-
+
-
404
-
Oops! Page not found
-
- Return to Home
-
+
Seite nicht gefunden
+
Diese Seite gibt es nicht oder nicht mehr.
+
+ Zur Startseite
+
);
diff --git a/src/test/sofort-paket.test.tsx b/src/test/sofort-paket.test.tsx
index 652f2d7..f0e0b71 100644
--- a/src/test/sofort-paket.test.tsx
+++ b/src/test/sofort-paket.test.tsx
@@ -282,3 +282,71 @@ describe("C1 Footer auf allen Unterseiten", () => {
expect(inhalt.compareDocumentPosition(footer) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
});
+
+// ── Kontrast-Hilfen (WCAG 2.x), gelesen werden die Farb-Token aus src/index.css ──
+type RGB = [number, number, number];
+
+const rgbAusHsl = (h: number, s: number, l: number): RGB => {
+ s /= 100;
+ l /= 100;
+ const k = (n: number) => (n + h / 30) % 12;
+ const a = s * Math.min(l, 1 - l);
+ const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
+ return [f(0) * 255, f(8) * 255, f(4) * 255];
+};
+const leuchtdichte = (rgb: RGB) => {
+ const [r, g, b] = rgb.map((v) => {
+ const c = v / 255;
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
+ });
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+};
+const kontrast = (a: RGB, b: RGB) => {
+ const [hell, dunkel] = [leuchtdichte(a), leuchtdichte(b)].sort((x, y) => y - x);
+ return (hell + 0.05) / (dunkel + 0.05);
+};
+/** "#120F17" oder "hsl(0 0% 6%)" als RGB. */
+const farbe = (wert: string): RGB => {
+ const hex = wert.trim().match(/^#([0-9a-f]{6})$/i);
+ if (hex) return [0, 2, 4].map((i) => parseInt(hex[1].slice(i, i + 2), 16)) as RGB;
+ const hsl = wert.trim().match(/^hsl\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%\s*\)$/);
+ if (hsl) return rgbAusHsl(Number(hsl[1]), Number(hsl[2]), Number(hsl[3]));
+ throw new Error(`Unbekanntes Farbformat: ${wert}`);
+};
+type Thema = "hell" | "dunkel";
+/** Farb-Token ("--foreground: 0 0% 9%") eines Themes aus index.css. */
+const tokenFarbe = (thema: Thema, name: string): RGB => {
+ const css = lies("src/index.css");
+ const block = css.match(thema === "dunkel" ? /\.dark\s*\{([^}]*)\}/ : /:root\s*\{([^}]*)\}/)?.[1] ?? "";
+ const wert = block.match(new RegExp(`--${name}:\\s*([\\d.]+)\\s+([\\d.]+)%\\s+([\\d.]+)%`));
+ if (!wert) throw new Error(`Token --${name} fehlt im Theme ${thema}`);
+ return rgbAusHsl(Number(wert[1]), Number(wert[2]), Number(wert[3]));
+};
+const THEMEN: Thema[] = ["hell", "dunkel"];
+
+describe("C5 404-Seite", () => {
+ it("zeigt Überschrift, Erklärung und Link zur Startseite auf Deutsch", () => {
+ vi.spyOn(console, "error").mockImplementation(() => {});
+ seite("/gibt-es-nicht");
+ expect(screen.getByRole("heading", { level: 1, name: "Seite nicht gefunden" })).toBeInTheDocument();
+ expect(screen.getByText("Diese Seite gibt es nicht oder nicht mehr.")).toBeInTheDocument();
+ expect(screen.getByRole("link", { name: "Zur Startseite" })).toHaveAttribute("href", "/");
+ expect(screen.queryByText(/Oops|Return to Home|Page not found/)).toBeNull();
+ });
+
+ it("nutzt die Theme-Farben statt eines festen grauen Hintergrunds", () => {
+ vi.spyOn(console, "error").mockImplementation(() => {});
+ const { container } = seite("/gibt-es-nicht");
+ expect(container.querySelector(".bg-background.text-foreground")).not.toBeNull();
+ expect(container.querySelector(".bg-muted")).toBeNull();
+ expect(screen.getByRole("link", { name: "Zur Startseite" })).toHaveClass("text-foreground");
+ });
+
+ it("Textfarben der Seite erreichen auf dem Hintergrund in beiden Themes mindestens 4,5:1", () => {
+ for (const thema of THEMEN) {
+ for (const name of ["foreground", "muted-foreground"]) {
+ expect(kontrast(tokenFarbe(thema, name), tokenFarbe(thema, "background"))).toBeGreaterThanOrEqual(4.5);
+ }
+ }
+ });
+});