diff --git a/app/.metadata b/app/.metadata index 32850fc..a49b961 100644 --- a/app/.metadata +++ b/app/.metadata @@ -15,7 +15,7 @@ migration: - platform: root create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 - - platform: macos + - platform: web create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 diff --git a/app/lib/pages/legal_page.dart b/app/lib/pages/legal_page.dart new file mode 100644 index 0000000..5380740 --- /dev/null +++ b/app/lib/pages/legal_page.dart @@ -0,0 +1,334 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../theme/reclaim_tokens.dart'; +import '../widgets/reclaim_card.dart'; + +/// Combined legal-information page. Six sections in the order +/// a careful reader cares about them: +/// +/// 1. Disclaimer (kein Rechtsrat) — top, most visible +/// 2. EU AI Act Art. 50 (Transparenz) +/// 3. Quellen + Urheberrecht (§ 5 UrhG) +/// 4. Datenschutz (DSGVO Art. 13) +/// 5. Impressum (§ 5 TMG, § 18 MStV) +/// 6. Open Source + Lizenz +/// +/// Each section carries placeholder markers `[PLACEHOLDER]` +/// where the deployment-specific value (Anbieter-Adresse, +/// AVV-Stand, Hosting-Provider) must be filled in before any +/// public publication. The text is drafted conservatively; +/// review by einem Volljuristen bleibt obligatorisch vor der +/// ersten öffentlichen Veröffentlichung. +class LegalPage extends StatelessWidget { + const LegalPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = Theme.of(context).textTheme; + return SingleChildScrollView( + padding: const EdgeInsets.all(ReclaimSpace.xl), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Recht & Hinweise', style: t.displaySmall), + const SizedBox(height: ReclaimSpace.xs), + Text( + 'Disclaimer, AI-Act-Transparenz, Urheberrecht, ' + 'Datenschutz, Impressum, Lizenz.', + style: t.bodyLarge?.copyWith(color: ReclaimColors.mute), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 1. Disclaimer + ReclaimCard( + accent: true, + child: _Section( + title: '1. Disclaimer — keine Rechtsberatung', + children: [ + _p(t, + 'Recl∆Im liefert eine methodisch-strukturierte ' + 'Wirkungsanalyse von Rechtsnormen. Die Ergebnisse sind ' + 'KEINE Rechtsberatung im Sinne des ' + 'Rechtsdienstleistungsgesetzes (RDG) und ersetzen weder ' + 'eine individuelle juristische Prüfung noch eine ' + 'Auskunft der zuständigen Behörden oder Kammern.'), + _p(t, + 'Alle Bewertungs-Zahlen tragen eine Evidenz-Tier-Stufe ' + '(T1–T4). Stufen T3/T4 sind nicht peer-reviewed und ' + 'dienen ausschließlich als Hinweis-/Anregung-Wert. ' + 'Eine Reform- oder Streichungs-Empfehlung der ' + 'Anwendung ist ein methodischer Vorschlag, keine ' + 'politische Forderung.'), + _p(t, + 'Die Demo-Daten (fünf Pilot-Normen) sind ' + 'Plausibilitäts-Schätzungen ohne NKR-Validierung. ' + 'Bevor eine Zahl in eine politische Vorlage übernommen ' + 'wird, ist die zugrundeliegende Quelle einzeln zu ' + 'prüfen.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 2. AI Act + ReclaimCard( + child: _Section( + title: + '2. EU AI Act — Transparenz nach Art. 50', + children: [ + _p(t, + 'Recl∆Im setzt Künstliche-Intelligenz-Komponenten ein: ' + 'Large-Language-Modelle (LLM) zur Vorschlags-Extraktion ' + 'aus Norm-Texten und zur Synthese von Reform-Texten. ' + 'Jede LLM-erzeugte Aussage ist kennzeichnungspflichtig ' + 'und mit RAG-Quellen-Pflicht hinterlegt; Aussagen ohne ' + 'zitierfähige Quelle werden verworfen.'), + _p(t, + 'Recl∆Im ist KEIN Hochrisiko-KI-System nach Anhang III ' + 'der KI-Verordnung. Die Anwendung trifft keine ' + 'Verwaltungsentscheidungen, generiert keine Rechtsakte, ' + 'beeinflusst nicht direkt Beschäftigungs-, Bildungs- ' + 'oder Sozialleistungs-Verfahren. Sie dient ausschließlich ' + 'der Recherche, Visualisierung und Vorschlags-Generierung ' + 'für menschliche Entscheidungsträger.'), + _p(t, + 'Jede Bewertung durchläuft vor Veröffentlichung einen ' + '`system.approval@^0`-Schritt (Human-in-the-Loop). ' + 'Die menschliche Letztverantwortung ist nicht ' + 'überschreibbar.'), + _p(t, + 'Verwendete Modelle und ihre Digests werden im ' + 'tamper-evidenten Audit-Log des Ch∆In-Hubs gespeichert. ' + 'Reproduzierbarkeit erfolgt mit Temperature ≤ 0,1 und ' + 'festem Prompt-Hash.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 3. Quellen + UrhG + ReclaimCard( + child: _Section( + title: '3. Quellen & Urheberrecht (§ 5 UrhG)', + children: [ + _p(t, + 'Die in der Anwendung dargestellten Gesetzes- und ' + 'Verordnungs-Texte sind amtliche Werke im Sinne des ' + '§ 5 UrhG und damit gemeinfrei. Die kanonischen Quellen ' + 'sind verlinkt und durch SHA-256-Prüfsummen im ' + 'Audit-Log nachvollziehbar.'), + _p(t, + 'Primärquellen (T1):'), + _bullet(t, + 'Bundesrecht: gesetze-im-internet.de (BMJ) — § 5 UrhG.'), + _bullet(t, + 'EU-Recht: EUR-Lex (Europäische Kommission) — ' + 'Mehrsprachigkeit nach Verordnung (EU) Nr. 1257/2012.'), + _bullet(t, + 'Landesrecht Berlin: gesetze.berlin.de / ' + 'landesrecht-berlin.de — § 5 UrhG.'), + _p(t, + 'Sekundärquellen (T1/T2):'), + _bullet(t, + 'Nationaler Normenkontrollrat (NKR) — ' + 'Bürokratiekosten-Datenbank, Methodenleitfaden.'), + _bullet(t, + 'Statistisches Bundesamt (DESTATIS) — ' + 'Unternehmensregister, Bürokratiekostenindex.'), + _bullet(t, + 'DIHK, ZDH, IHK Berlin, BDI — ' + 'Verbands-Erhebungen mit Quellen-Angabe.'), + _p(t, + 'Eigene Methodik und Software-Code sind unter ' + 'Apache License 2.0 veröffentlicht; Code-Repository: ' + 'git.flemming.ai/fai/reclaim.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 4. Datenschutz + ReclaimCard( + child: _Section( + title: + '4. Datenschutzerklärung (DSGVO Art. 13)', + children: [ + _p(t, + 'Verantwortlicher i.S.d. Art. 4 Nr. 7 DSGVO: ' + '[PLACEHOLDER — Name und Anschrift des Anbieters ' + 'einsetzen].'), + _p(t, + 'Diese Anwendung verarbeitet personenbezogene Daten ' + 'nur in dem Umfang, der für ihren Betrieb erforderlich ' + 'ist. In der Demo-Konfiguration werden keine ' + 'personenbezogenen Daten erhoben; alle Pilot-Norm-Daten ' + 'sind lokal im Browser/auf dem Endgerät gespeichert.'), + _p(t, 'Beim Aufruf der Anwendung über das Internet:'), + _bullet(t, + 'Hosting-Provider: [PLACEHOLDER — etwa Hetzner Online ' + 'GmbH, Falkenstein, EU-Hosting]. AVV nach Art. 28 ' + 'DSGVO abgeschlossen.'), + _bullet(t, + 'Server-Logs (IP-Adresse, User-Agent, Zeitstempel): ' + 'für 7 Tage zur Missbrauchs-Abwehr (Rechtsgrundlage ' + 'Art. 6 Abs. 1 lit. f DSGVO, berechtigtes Interesse).'), + _bullet(t, + 'Kein Tracking, keine Analytics, keine ' + 'Marketing-Cookies.'), + _p(t, + 'Betroffenenrechte (Art. 15–22 DSGVO): Auskunft, ' + 'Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, ' + 'Widerspruch. Kontakt: ' + '[PLACEHOLDER — Kontakt-E-Mail].'), + _p(t, + 'Beschwerderecht bei der Aufsichtsbehörde: ' + 'Berliner Beauftragte für Datenschutz und ' + 'Informationsfreiheit, Friedrichstr. 219, 10969 Berlin, ' + 'mailbox@datenschutz-berlin.de.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 5. Impressum + ReclaimCard( + child: _Section( + title: + '5. Impressum (§ 5 TMG, § 18 Abs. 2 MStV)', + children: [ + _p(t, + 'Anbieter: [PLACEHOLDER — Name].'), + _p(t, + 'Anschrift: [PLACEHOLDER — Straße, Hausnummer, ' + 'PLZ, Ort, Land].'), + _p(t, + 'Kontakt: [PLACEHOLDER — E-Mail, ggf. Telefon].'), + _p(t, + 'Vertretungsberechtigt: [PLACEHOLDER — bei Vereinen, ' + 'GmbHs etc.].'), + _p(t, + 'Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV: ' + '[PLACEHOLDER — Name und ladungsfähige Anschrift, ' + 'in der Regel identisch mit Anbieter].'), + _p(t, + 'Umsatzsteuer-Identifikationsnummer / Register-' + 'eintrag, sofern einschlägig: [PLACEHOLDER].'), + _p(t, + 'Plattform der Europäischen Kommission zur ' + 'außergerichtlichen Online-Streitbeilegung (Art. 14 ' + 'Abs. 1 ODR-VO): ec.europa.eu/consumers/odr. Wir sind ' + 'nicht bereit oder verpflichtet, an ' + 'Streitbeilegungsverfahren vor einer ' + 'Verbraucherschlichtungsstelle teilzunehmen.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.lg), + + // 6. Open Source + ReclaimCard( + child: _Section( + title: '6. Open Source & Lizenz', + children: [ + _p(t, + 'Recl∆Im ist freie Software, lizenziert unter der ' + 'Apache License, Version 2.0. Der Quelltext, die ' + 'Module und die Methodik sind öffentlich einsehbar ' + 'unter:'), + InkWell( + onTap: () => launchUrl( + Uri.parse('https://git.flemming.ai/fai/reclaim'), + mode: LaunchMode.externalApplication, + ), + child: Text( + 'https://git.flemming.ai/fai/reclaim', + style: t.bodyLarge?.copyWith( + color: ReclaimColors.signal, + decoration: TextDecoration.underline, + fontFamily: ReclaimTypography.mono, + ), + ), + ), + const SizedBox(height: ReclaimSpace.sm), + _p(t, + 'Plattform: Ch∆In (Apache 2.0, ' + 'git.flemming.ai/fai/chain-private). Methodische ' + 'Vorbilder und Lizenz-Hinweise stehen in der Datei ' + 'NOTICE im Repository.'), + _p(t, + 'Beiträge willkommen. DCO Sign-off und Conventional ' + 'Commits in Englisch sind Konvention.'), + ], + ), + ), + const SizedBox(height: ReclaimSpace.xxl), + + Text( + 'Hinweis: Vor der ersten öffentlichen Veröffentlichung ' + 'müssen alle [PLACEHOLDER]-Stellen durch konkrete ' + 'Anbieter-Angaben ersetzt und das gesamte Dokument von ' + 'einem Volljuristen geprüft werden. Diese Seite ist eine ' + 'methodische Vorlage.', + style: t.labelSmall?.copyWith( + color: ReclaimColors.tierT4, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ); + } +} + +class _Section extends StatelessWidget { + const _Section({required this.title, required this.children}); + + final String title; + final List children; + + @override + Widget build(BuildContext context) { + final t = Theme.of(context).textTheme; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: t.headlineSmall), + const SizedBox(height: ReclaimSpace.md), + ...children, + ], + ); + } +} + +Widget _p(TextTheme t, String s) => Padding( + padding: const EdgeInsets.only(bottom: ReclaimSpace.sm), + child: SelectableText(s, style: t.bodyLarge?.copyWith(height: 1.55)), + ); + +Widget _bullet(TextTheme t, String s) => Padding( + padding: const EdgeInsets.only( + left: ReclaimSpace.lg, + bottom: ReclaimSpace.xs, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 8, right: 10), + child: Container( + width: 5, + height: 5, + decoration: const BoxDecoration( + color: ReclaimColors.signal, + shape: BoxShape.circle, + ), + ), + ), + Expanded( + child: SelectableText(s, + style: t.bodyLarge?.copyWith(height: 1.55)), + ), + ], + ), + ); diff --git a/app/lib/pages/shell_page.dart b/app/lib/pages/shell_page.dart index fcbebde..dfbfd3f 100644 --- a/app/lib/pages/shell_page.dart +++ b/app/lib/pages/shell_page.dart @@ -6,6 +6,7 @@ import '../widgets/delta_mark.dart'; import 'about_page.dart'; import 'heatmap_page.dart'; import 'hub_status_page.dart'; +import 'legal_page.dart'; import 'norms_list_page.dart'; /// App shell with a NavigationRail. Each rail destination is one @@ -30,6 +31,7 @@ class _ShellPageState extends State { NormsListPage(repository: widget.repository), HubStatusPage(repository: widget.repository), const AboutPage(), + const LegalPage(), ]; return Scaffold( @@ -80,6 +82,11 @@ class _ShellPageState extends State { selectedIcon: Icon(Icons.info), label: Text('Methodik'), ), + NavigationRailDestination( + icon: Icon(Icons.gavel_outlined), + selectedIcon: Icon(Icons.gavel), + label: Text('Recht'), + ), ], ), const VerticalDivider(width: 1), diff --git a/app/lib/widgets/demo_banner.dart b/app/lib/widgets/demo_banner.dart index 0d41fc0..dcf8537 100644 --- a/app/lib/widgets/demo_banner.dart +++ b/app/lib/widgets/demo_banner.dart @@ -32,9 +32,9 @@ class DemoBanner extends StatelessWidget { const SizedBox(width: ReclaimSpace.sm), Expanded( child: Text( - 'Demo-Daten — Phase 0, ohne Verbands-Erhebung. ' - 'Keine Juristen-Approval, keine Beirats-Validierung. ' - 'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.', + 'Demo-Daten · KEINE Rechtsberatung · Tier T4. ' + 'Phase 0, ohne Verbands-Erhebung, ohne Juristen- oder ' + 'Beirats-Validierung. Volle Hinweise unter „Recht".', style: t.labelSmall?.copyWith( color: ReclaimColors.tierT4, ), diff --git a/app/web/favicon.png b/app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/app/web/favicon.png differ diff --git a/app/web/icons/Icon-192.png b/app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/app/web/icons/Icon-192.png differ diff --git a/app/web/icons/Icon-512.png b/app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/app/web/icons/Icon-512.png differ diff --git a/app/web/icons/Icon-maskable-192.png b/app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/app/web/icons/Icon-maskable-192.png differ diff --git a/app/web/icons/Icon-maskable-512.png b/app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/app/web/icons/Icon-maskable-512.png differ diff --git a/app/web/index.html b/app/web/index.html new file mode 100644 index 0000000..ba23a71 --- /dev/null +++ b/app/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + reclaim_app + + + + + + + diff --git a/app/web/manifest.json b/app/web/manifest.json new file mode 100644 index 0000000..2bcfb6d --- /dev/null +++ b/app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "reclaim_app", + "short_name": "reclaim_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}