@@ -326,7 +326,7 @@ export default function AdminDashboard() { {incompleteCount > 0 && ( <> {" · "} - + {incompleteCount} unvollständig > @@ -334,30 +334,40 @@ export default function AdminDashboard() {
-
+
-
- setSearchTerm(e.target.value)}
- className="px-9"
- />
- {searchTerm && (
-
- )}
+
{missingByPolicy.get(policy.id)!.length}
diff --git a/app/auth/signin/page.tsx b/app/auth/signin/page.tsx
index a58d1ff..6589001 100644
--- a/app/auth/signin/page.tsx
+++ b/app/auth/signin/page.tsx
@@ -55,7 +55,7 @@ export default function SignInPage() {
-
+
@@ -448,7 +458,7 @@ export default function AdminDashboard() {
+
+
-
+
+ setSearchTerm(e.target.value)}
+ className="px-9"
+ />
+ {searchTerm && (
+
+ )}
+
diff --git a/app/erklaerung/[slug]/page.tsx b/app/erklaerung/[slug]/page.tsx
index 7ece6e3..5c85276 100644
--- a/app/erklaerung/[slug]/page.tsx
+++ b/app/erklaerung/[slug]/page.tsx
@@ -129,7 +129,7 @@ export default async function PolicyPage({
-
+
diff --git a/app/gebaerdensprache/page.tsx b/app/gebaerdensprache/page.tsx
new file mode 100644
index 0000000..7d4d86d
--- /dev/null
+++ b/app/gebaerdensprache/page.tsx
@@ -0,0 +1,10 @@
+import { LegalPage, legalPageMetadata } from "@/components/legal-page"
+
+// Inhalt kommt aus der Datenbank und wird im Admin gepflegt.
+export const dynamic = "force-dynamic"
+
+export const metadata = legalPageMetadata("signLanguage")
+
+export default function Page() {
+ return
+}
diff --git a/app/globals.css b/app/globals.css
index ed92dea..7fb2c1a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -17,16 +17,19 @@
--secondary: oklch(0.968 0.002 286);
--secondary-foreground: oklch(0.21 0.006 286);
--muted: oklch(0.968 0.002 286);
- --muted-foreground: oklch(0.552 0.014 286);
+ /* 4.55:1 auch auf muted-Flächen – vorher 0.552 und damit knapp darunter */
+ --muted-foreground: oklch(0.545 0.014 286);
--accent: oklch(0.968 0.002 286);
--accent-foreground: oklch(0.21 0.006 286);
- --destructive: oklch(0.577 0.245 27.325);
+ --destructive: oklch(0.520 0.245 27.325); /* 4.5:1 auch auf 10%-Fläche im Badge */
--destructive-foreground: oklch(0.985 0 0);
- --success: oklch(0.596 0.145 163);
+ --success: oklch(0.495 0.145 163); /* 4.5:1 auch auf 10%-Fläche im Badge */
--border: oklch(0.918 0.004 286);
- --input: oklch(0.918 0.004 286);
- --ring: oklch(0.6 0.012 286);
- --brand: oklch(0.55 0.155 258);
+ /* Rahmen von Eingabefeldern: 3:1 nach WCAG 1.4.11, sonst ist das Feld
+ nicht als solches erkennbar. Dekorative Trenner nutzen --border. */
+ --input: oklch(0.620 0.004 286);
+ --ring: oklch(0.53 0.155 258); /* Fokus in Markenfarbe, 5.2:1 */
+ --brand: oklch(0.53 0.155 258); /* 4.5:1 auch auf 10%-Fläche im Badge */
--brand-foreground: oklch(0.985 0 0);
--chart-1: oklch(0.55 0.155 258);
--chart-2: oklch(0.6 0.118 184.704);
@@ -58,12 +61,12 @@
--muted-foreground: oklch(0.712 0.013 286);
--accent: oklch(0.256 0.006 286);
--accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.637 0.237 25.331);
+ --destructive: oklch(0.670 0.237 25.331); /* 4.5:1 auf 20%-Fläche im Badge */
--destructive-foreground: oklch(0.985 0 0);
--success: oklch(0.696 0.17 162.48);
--border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.55 0.012 286);
+ --input: oklch(0.500 0.006 286); /* deckend statt transparent, 3:1 */
+ --ring: oklch(0.68 0.14 258); /* Fokus in Markenfarbe, 6.9:1 */
--brand: oklch(0.68 0.14 258);
--brand-foreground: oklch(0.14 0.004 286);
--chart-1: oklch(0.68 0.14 258);
@@ -148,7 +151,7 @@
@layer base {
* {
- @apply border-border outline-ring/50;
+ @apply border-border outline-ring;
}
body {
diff --git a/app/layout.tsx b/app/layout.tsx
index 6896050..b8ea14e 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,6 +4,7 @@ import localFont from "next/font/local"
import { AppConfigProvider } from "@/components/providers/app-config-provider"
import { AuthProvider } from "@/components/providers/session-provider"
import { appName } from "@/lib/app-config"
+import { SkipLink } from "@/components/skip-link"
import { Toaster } from "@/components/ui/sonner"
import "./globals.css"
@@ -56,6 +57,7 @@ export default function RootLayout({
/>
+
{children}
diff --git a/app/leichte-sprache/page.tsx b/app/leichte-sprache/page.tsx
new file mode 100644
index 0000000..a294fe3
--- /dev/null
+++ b/app/leichte-sprache/page.tsx
@@ -0,0 +1,10 @@
+import { LegalPage, legalPageMetadata } from "@/components/legal-page"
+
+// Inhalt kommt aus der Datenbank und wird im Admin gepflegt.
+export const dynamic = "force-dynamic"
+
+export const metadata = legalPageMetadata("easyLanguage")
+
+export default function Page() {
+ return
+}
diff --git a/components/defaultable-field.tsx b/components/defaultable-field.tsx
index 26a0cdd..bde357d 100644
--- a/components/defaultable-field.tsx
+++ b/components/defaultable-field.tsx
@@ -70,7 +70,7 @@ export function DefaultableField({
invalid={invalid}
/>
-
+
Weicht vom Standard ab
-
+
+
-
) : (
-
{meta.title}
diff --git a/components/policy-list.tsx b/components/policy-list.tsx index 1623397..1250a82 100644 --- a/components/policy-list.tsx +++ b/components/policy-list.tsx @@ -30,7 +30,7 @@ export function PolicyList({ initial }: { initial: PrivacyPolicy[] }) { }, [policies, searchTerm]) return ( -@@ -41,25 +41,35 @@ export function PolicyList({ initial }: { initial: PrivacyPolicy[] }) {
-
- setSearchTerm(e.target.value)}
- className="px-9"
- />
- {searchTerm && (
- setSearchTerm("")}
- className="absolute top-1/2 right-0.5 -translate-y-1/2 text-muted-foreground"
- >
-
- Suche zurücksetzen
-
- )}
+
@@ -102,60 +112,58 @@ export function PolicyList({ initial }: { initial: PrivacyPolicy[] }) {
)}
+
+
+
+ setSearchTerm(e.target.value)}
+ className="px-9"
+ />
+ {searchTerm && (
+ setSearchTerm("")}
+ className="absolute top-1/2 right-0.5 -translate-y-1/2 text-muted-foreground"
+ >
+
+ Suche zurücksetzen
+
+ )}
+
+
-
{filteredPolicies.map((policy) => (
-
- {/* Echter Link: teilbar, in neuem Tab zu öffnen, ohne JavaScript nutzbar */}
+ {/* Echter Link: teilbar, in neuem Tab zu öffnen, ohne JavaScript
+ nutzbar. Der Pfeil gehört mit hinein, statt daneben ein
+ zweiter Link auf dasselbe Ziel zu stehen. */}
-
+
-
- {policy.title}
-
+ {policy.title}
{policy.responsible.replace(/[*_#`]/g, "").split("\n")[0]}
+
-
+
-
+
+
))}
-
+
+ Zuletzt geändert:
{formatDate(policy.updatedAt)}
-
+
-
-
-
- {policy.title} öffnen
-
-
-
-
+
-
- {!isIconOnly && label}
+
+ {isIconOnly ? (
+ // Ein Tooltip ist kein zugänglicher Name – ohne diesen Text wäre die
+ // Schaltfläche für Screenreader namenlos. Der Titel steht mit drin,
+ // weil in der Liste mehrere gleich aussehende Schaltflächen stehen.
+ {`${label}: ${policy.title}`}
+ ) : (
+ label
+ )}
)
diff --git a/components/public-header.tsx b/components/public-header.tsx
index 5f72484..6365ce5 100644
--- a/components/public-header.tsx
+++ b/components/public-header.tsx
@@ -12,8 +12,8 @@ import { ThemeToggle } from "@/components/theme-toggle"
export function PublicHeader({ subtitle }: { subtitle?: string }) {
return (
-
+ {/* Beim Filtern hört eine Vorlesesoftware sonst nicht, was passiert */}
+ DSGVO-konform
)}
diff --git a/components/print-policy.tsx b/components/print-policy.tsx
index 3dc89c7..77fa0c1 100644
--- a/components/print-policy.tsx
+++ b/components/print-policy.tsx
@@ -39,8 +39,15 @@ export function PrintPolicy({
const button = (
{filteredPolicies.length}{" "} {filteredPolicies.length === 1 ? "Erklärung" : "Erklärungen"} {searchTerm && ` von ${policies.length}`} - +
-
+
+
Zur Übersicht
diff --git a/components/site-footer.tsx b/components/site-footer.tsx
index d32ff16..db03542 100644
--- a/components/site-footer.tsx
+++ b/components/site-footer.tsx
@@ -18,7 +18,7 @@ export async function SiteFooter() {
return (