From 3633e8a4cf98f8d8e02f5d73b610d2106c65088e Mon Sep 17 00:00:00 2001 From: Weapie Date: Tue, 8 Sep 2026 15:44:30 +0200 Subject: [PATCH] Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rechtsrahmen: § 13 Abs. 3 LBGG SH i. V. m. §§ 3 und 4 BITV 2.0. Eine Erklaerung "nicht barrierefrei" ersetzt die Herstellungspflicht nicht, sie dokumentiert den Stand nur voruebergehend. Farben, alle Werte gegen die WCAG-Formel nachgerechnet: - Sekundaertext 4.40:1 auf muted-Flaechen -> 4.55:1 - Rahmen von Eingabefeldern 1.24:1 -> 3:1 (WCAG 1.4.11); dekorative Trenner bleiben unveraendert - Badge-Farben scheiterten auf der eigenen 10-Prozent-Flaeche (brand 4.20, success 4.18, destructive 3.88) -> jeweils ueber 4.5 - Warnfarbe im Admin 3.11:1 -> amber-700 mit 4.90:1 Fokus war faktisch unsichtbar: Der Ring wurde durchgaengig mit halber Deckkraft gezeichnet (ring-ring/50), effektiv rund 1.5:1. Jetzt volle Deckkraft in Markenfarbe, 12 Dateien betroffen. Weiter: Sprungmarke zum Inhalt auf allen Seiten, Suchfelder beschriftet, Druck-Schaltflaechen hatten als reine Icon-Buttons gar keinen zugaenglichen Namen, Liste als echte Liste ausgezeichnet, doppelter Link je Zeile entfernt, Trefferzahl als Live-Region. Responsiv: Aussenabstaende und Kopfzeilen ab 320 Pixel, Listenzeilen brechen um. Neu nach § 4 BITV 2.0: Seiten fuer Leichte Sprache und Gebaerdensprache, im Admin pflegbar. Leer gelassen und damit noch nicht online - die Inhalte kommen vom Amt. In der Erklaerung als bekannte Luecke benannt. Geprueft mit axe-core (WCAG 2.1 A und AA) ueber alle oeffentlichen Seiten und den Adminbereich, hell und dunkel, dazu Tastaturbedienung, 200 Prozent Vergroesserung und 320 Pixel Breite: keine Verstoesse, kein waagerechter Ueberlauf. Co-Authored-By: Claude Opus 5 --- app/admin/page.tsx | 62 ++++++----- app/auth/signin/page.tsx | 2 +- app/erklaerung/[slug]/page.tsx | 2 +- app/gebaerdensprache/page.tsx | 10 ++ app/globals.css | 23 ++-- app/layout.tsx | 2 + app/leichte-sprache/page.tsx | 10 ++ components/defaultable-field.tsx | 2 +- components/legal-page.tsx | 2 +- components/policy-list.tsx | 104 ++++++++++-------- components/print-policy.tsx | 11 +- components/public-header.tsx | 4 +- components/site-footer.tsx | 2 +- components/site-pages-form.tsx | 63 +++++++++-- components/skip-link.tsx | 17 +++ components/ui/accordion.tsx | 2 +- components/ui/badge.tsx | 2 +- components/ui/button.tsx | 4 +- components/ui/input.tsx | 2 +- components/ui/scroll-area.tsx | 2 +- components/ui/select.tsx | 2 +- components/ui/switch.tsx | 2 +- components/ui/tabs.tsx | 2 +- components/ui/textarea.tsx | 2 +- lib/site-pages.ts | 41 ++++++- .../migration.sql | 8 ++ prisma/schema.prisma | 4 +- 27 files changed, 272 insertions(+), 117 deletions(-) create mode 100644 app/gebaerdensprache/page.tsx create mode 100644 app/leichte-sprache/page.tsx create mode 100644 components/skip-link.tsx create mode 100644 prisma/migrations/20260908120000_site_pages_barrierefreiheit/migration.sql diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 79f4c67..7c86371 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -217,7 +217,7 @@ export default function AdminDashboard() { return (
-
+
Admin @@ -314,7 +314,7 @@ export default function AdminDashboard() {
-
+

@@ -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 && ( - - )} +
+
+ +
+
-
@@ -448,7 +458,7 @@ export default function AdminDashboard() { {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() {
-
+
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({
-
+
- )} +
+ +
+
@@ -102,60 +112,58 @@ export function PolicyList({ initial }: { initial: PrivacyPolicy[] }) { )}
) : ( -
+
    {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]} +
    +
)} {!isLoading && !error && filteredPolicies.length > 0 && ( -
- +
+ {/* Beim Filtern hört eine Vorlesesoftware sonst nicht, was passiert */} +

{filteredPolicies.length}{" "} {filteredPolicies.length === 1 ? "Erklärung" : "Erklärungen"} {searchTerm && ` von ${policies.length}`} - +

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 = ( ) 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 (
-
- +
+ 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 (