feat(brand): rename product to Recl∆Im (Reclaim) across app, bundle, docs

Recl∆Im — the F∆I product line for Pflicht-basierte
Wirkungsanalyse und Reform-Vorschlags-Pipelines. Replaces the
working title 'Law-Heatmap', which described the demo view
rather than the platform. Heatmap, Norms list, Hub view,
Methodik are now four panes of the same Recl∆Im app.

Renames in this commit:

  Flutter package      lawheatmap_app   -> reclaim_app
  macOS bundle id      ai.flemming.lawheatmapApp -> ai.flemming.reclaim
  macOS PRODUCT_NAME   lawheatmap_app   -> reclaim_app
  Theme tokens         LawHeatmapColors/Space/Radius/Typography/Theme
                       -> Reclaim*
  Surface card         LawHeatmapCard   -> ReclaimCard
  Top-level widget     LawHeatmapApp    -> ReclaimApp
  Theme files          lawheatmap_theme.dart/lawheatmap_tokens.dart
                       -> reclaim_theme.dart/reclaim_tokens.dart
  Widget file          lawheatmap_card.dart -> reclaim_card.dart
  Build script         build-macos.sh paths and headline string
  Docs                 MACHBARKEITSSTUDIE.md, METHODIK.md, RUN.md,
                       app/README.md, flow YAML
  UI strings           'F∆I Law-Heatmap' / 'Law-Heatmap'
                       -> 'Recl∆Im' / 'Recl∆Im (F∆I)' for vendor-
                       prefixed contexts

flutter analyze: clean. flutter test: 2/2 passing.

The local working directory stays fai_lawheatmap/ for now —
the dir is referenced by the .claude project metadata; renaming
it would break session continuity for no real gain. The Forgejo
repo was renamed via API in the same change-set (fai/lawheatmap
-> fai/reclaim), with the legacy slug serving a redirect
courtesy of Forgejo's built-in rename handling.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-18 13:42:55 +02:00
parent 90b7500aaf
commit e08cedcd3f
29 changed files with 291 additions and 226 deletions

View file

@ -1,4 +1,4 @@
# F∆I Law-Heatmap — Machbarkeitsstudie & Umsetzungsplan # Recl∆Im (F∆I) — Machbarkeitsstudie & Umsetzungsplan
> Arbeitstitel des Systems: **„Gesetzes-Heatmap" / „Regulierungs-Wirkungsanalyse auf Ch∆In-Basis"** > Arbeitstitel des Systems: **„Gesetzes-Heatmap" / „Regulierungs-Wirkungsanalyse auf Ch∆In-Basis"**
> Auftraggeber-Kontext: MIT AG Digitalisierung & KI-Strategien (Vorsitz Dr. S. Flemming), > Auftraggeber-Kontext: MIT AG Digitalisierung & KI-Strategien (Vorsitz Dr. S. Flemming),

View file

@ -1,6 +1,6 @@
# Methodik-Papier — Pflicht-basierte Wirkungsanalyse von Rechtsnormen # Methodik-Papier — Pflicht-basierte Wirkungsanalyse von Rechtsnormen
> Standalone-Auszug aus der Machbarkeitsstudie F∆I Law-Heatmap, > Standalone-Auszug aus der Machbarkeitsstudie Recl∆Im (F∆I),
> destilliert für AG-Vorlage und Beirats-Konsultation. > destilliert für AG-Vorlage und Beirats-Konsultation.
> >
> Version v0.1.0 · 2026-06 · Diskussionsgrundlage, AG-intern. > Version v0.1.0 · 2026-06 · Diskussionsgrundlage, AG-intern.

4
RUN.md
View file

@ -1,4 +1,4 @@
# F∆I Law-Heatmap — Run-Guide (Phase 0) # Recl∆Im (F∆I) — Run-Guide (Phase 0)
> Stand v0.1.0 — Demo-Modus, ohne externe Daten, ohne Hub-Verbindung. > Stand v0.1.0 — Demo-Modus, ohne externe Daten, ohne Hub-Verbindung.
@ -39,7 +39,7 @@ eigene Crate-Verzeichnisse, ABI `chain:platform`, alle Apache 2.0:
cd app cd app
./setup.sh # Fonts + flutter pub get ./setup.sh # Fonts + flutter pub get
./build-macos.sh # Robust-Build (strippt macOS-xattrs vor CodeSign) ./build-macos.sh # Robust-Build (strippt macOS-xattrs vor CodeSign)
open build/macos/Build/Products/Debug/lawheatmap_app.app open build/macos/Build/Products/Debug/reclaim_app.app
``` ```
Direkter `flutter run -d macos` funktioniert auch, kann aber bei Direkter `flutter run -d macos` funktioniert auch, kann aber bei

View file

@ -1,6 +1,6 @@
# lawheatmap_app # reclaim_app
F∆I Law-Heatmap client Recl∆Im (F∆I) client
## Getting Started ## Getting Started

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Robust macOS build for the F∆I Law-Heatmap client. # Robust macOS build for the Recl∆Im (F∆I) client.
# #
# macOS xattrs (especially `com.apple.provenance` on files that # macOS xattrs (especially `com.apple.provenance` on files that
# touched iCloud or were downloaded by curl) make Xcode's # touched iCloud or were downloaded by curl) make Xcode's
@ -30,7 +30,7 @@ flutter build macos --debug \
echo " flutter exited $rc — continuing to xattr+resign step in case the binary linked" echo " flutter exited $rc — continuing to xattr+resign step in case the binary linked"
} }
APP=build/macos/Build/Products/Debug/lawheatmap_app.app APP=build/macos/Build/Products/Debug/reclaim_app.app
if [ ! -d "$APP" ]; then if [ ! -d "$APP" ]; then
echo "ERROR: $APP does not exist — flutter build failed before linking" echo "ERROR: $APP does not exist — flutter build failed before linking"
exit 1 exit 1
@ -45,4 +45,4 @@ echo "[4/4] ad-hoc resign"
echo echo
echo "done. Launch with:" echo "done. Launch with:"
echo " open $APP" echo " open $APP"
echo " # or directly: $APP/Contents/MacOS/lawheatmap_app" echo " # or directly: $APP/Contents/MacOS/reclaim_app"

View file

@ -5,13 +5,13 @@ import 'data/hub_endpoint.dart';
import 'data/hub_repository.dart'; import 'data/hub_repository.dart';
import 'data/repository.dart'; import 'data/repository.dart';
import 'pages/landing_page.dart'; import 'pages/landing_page.dart';
import 'theme/lawheatmap_theme.dart'; import 'theme/reclaim_theme.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final settings = await HubSettings.load(); final settings = await HubSettings.load();
final repository = await _pickRepository(settings); final repository = await _pickRepository(settings);
runApp(LawHeatmapApp(repository: repository, settings: settings)); runApp(ReclaimApp(repository: repository, settings: settings));
} }
/// Decide which repository to start with. /// Decide which repository to start with.
@ -28,8 +28,8 @@ Future<EvaluationRepository> _pickRepository(HubSettings settings) async {
return const MockRepository(); return const MockRepository();
} }
class LawHeatmapApp extends StatelessWidget { class ReclaimApp extends StatelessWidget {
const LawHeatmapApp({ const ReclaimApp({
super.key, super.key,
this.repository = const MockRepository(), this.repository = const MockRepository(),
this.settings, this.settings,
@ -41,10 +41,10 @@ class LawHeatmapApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'F∆I Law-Heatmap', title: 'Recl∆Im',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: LawHeatmapTheme.light(), theme: ReclaimTheme.light(),
darkTheme: LawHeatmapTheme.dark(), darkTheme: ReclaimTheme.dark(),
themeMode: ThemeMode.dark, themeMode: ThemeMode.dark,
localizationsDelegates: const [ localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/lawheatmap_card.dart'; import '../widgets/reclaim_card.dart';
class AboutPage extends StatelessWidget { class AboutPage extends StatelessWidget {
const AboutPage({super.key}); const AboutPage({super.key});
@ -10,25 +10,25 @@ class AboutPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Theme.of(context).textTheme; final t = Theme.of(context).textTheme;
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.all(LawHeatmapSpace.xl), padding: const EdgeInsets.all(ReclaimSpace.xl),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Methodik & Quellen', style: t.displaySmall), Text('Methodik & Quellen', style: t.displaySmall),
const SizedBox(height: LawHeatmapSpace.xs), const SizedBox(height: ReclaimSpace.xs),
Text( Text(
'Wie das System bewertet, woran es sich messen lässt.', 'Wie das System bewertet, woran es sich messen lässt.',
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute), style: t.bodyLarge?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
accent: true, accent: true,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Drei nicht verhandelbare Sätze', Text('Drei nicht verhandelbare Sätze',
style: t.headlineSmall), style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.md), const SizedBox(height: ReclaimSpace.md),
Text( Text(
'1. Wir bewerten Pflichten, nicht Gesetze.\n' '1. Wir bewerten Pflichten, nicht Gesetze.\n'
'2. Jede Zahl trägt die Tier-Stufe ihres schwächsten ' '2. Jede Zahl trägt die Tier-Stufe ihres schwächsten '
@ -40,13 +40,13 @@ class AboutPage extends StatelessWidget {
], ],
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Methodische Vorbilder', style: t.headlineSmall), Text('Methodische Vorbilder', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
_bullet(t, _bullet(t,
'Standardkostenmodell (SKM) — Nationaler Normenkontrollrat. P × F × T × h.'), 'Standardkostenmodell (SKM) — Nationaler Normenkontrollrat. P × F × T × h.'),
_bullet(t, _bullet(t,
@ -62,13 +62,13 @@ class AboutPage extends StatelessWidget {
], ],
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('4-Tier-Evidenz-Modell', style: t.headlineSmall), Text('4-Tier-Evidenz-Modell', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
_bullet(t, _bullet(t,
'T1 — amtlich/peer-reviewed: NKR, DESTATIS, ifo, ZEW, BMWK, SVR, IfM Bonn.'), 'T1 — amtlich/peer-reviewed: NKR, DESTATIS, ifo, ZEW, BMWK, SVR, IfM Bonn.'),
_bullet(t, _bullet(t,
@ -80,13 +80,13 @@ class AboutPage extends StatelessWidget {
], ],
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Plattform', style: t.headlineSmall), Text('Plattform', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'Ch∆In v0.16.0 — Vendor: F∆I = Flemming.AI. ' 'Ch∆In v0.16.0 — Vendor: F∆I = Flemming.AI. '
'Hub. Module. Flow. Deterministisch, WASM-sandboxed, ' 'Hub. Module. Flow. Deterministisch, WASM-sandboxed, '
@ -104,7 +104,7 @@ class AboutPage extends StatelessWidget {
Widget _bullet(TextTheme t, String text) { Widget _bullet(TextTheme t, String text) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.sm), padding: const EdgeInsets.only(bottom: ReclaimSpace.sm),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -114,7 +114,7 @@ class AboutPage extends StatelessWidget {
width: 5, width: 5,
height: 5, height: 5,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: LawHeatmapColors.signal, color: ReclaimColors.signal,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
), ),

View file

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/demo_banner.dart'; import '../widgets/demo_banner.dart';
import '../widgets/heatmap_grid.dart'; import '../widgets/heatmap_grid.dart';
import '../widgets/lawheatmap_card.dart'; import '../widgets/reclaim_card.dart';
import '../widgets/tier_badge.dart'; import '../widgets/tier_badge.dart';
import 'norm_detail_page.dart'; import 'norm_detail_page.dart';
@ -35,7 +35,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
const DemoBanner(), const DemoBanner(),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(LawHeatmapSpace.xl), padding: const EdgeInsets.all(ReclaimSpace.xl),
child: FutureBuilder<List<Evaluation>>( child: FutureBuilder<List<Evaluation>>(
future: _future, future: _future,
builder: (context, snap) { builder: (context, snap) {
@ -48,18 +48,18 @@ class _HeatmapPageState extends State<HeatmapPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Heatmap', style: t.displaySmall), Text('Heatmap', style: t.displaySmall),
const SizedBox(height: LawHeatmapSpace.xs), const SizedBox(height: ReclaimSpace.xs),
Text( Text(
'X: jährliche Bürokratiekosten (€, log10). ' 'X: jährliche Bürokratiekosten (€, log10). '
'Y: Nutzen-Score 05. Punktgröße: Betroffenheit. ' 'Y: Nutzen-Score 05. Punktgröße: Betroffenheit. '
'Klick auf einen Punkt → Detail mit Pflichten + Quellen.', 'Klick auf einen Punkt → Detail mit Pflichten + Quellen.',
style: t.bodyLarge?.copyWith( style: t.bodyLarge?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
Expanded( Expanded(
child: LawHeatmapCard( child: ReclaimCard(
accent: true, accent: true,
expand: true, expand: true,
child: HeatmapGrid( child: HeatmapGrid(
@ -77,10 +77,10 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
Wrap( Wrap(
spacing: LawHeatmapSpace.md, spacing: ReclaimSpace.md,
runSpacing: LawHeatmapSpace.sm, runSpacing: ReclaimSpace.sm,
children: [ children: [
for (final e in items) for (final e in items)
InkWell( InkWell(
@ -94,10 +94,10 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
); );
}, },
child: LawHeatmapCard( child: ReclaimCard(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: LawHeatmapSpace.md, horizontal: ReclaimSpace.md,
vertical: LawHeatmapSpace.sm, vertical: ReclaimSpace.sm,
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -105,7 +105,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
TierBadge( TierBadge(
tier: e.tierLowest, compact: true), tier: e.tierLowest, compact: true),
const SizedBox( const SizedBox(
width: LawHeatmapSpace.sm), width: ReclaimSpace.sm),
Text( Text(
'${e.norm.jurabk} ${e.norm.paragraph}', '${e.norm.jurabk} ${e.norm.paragraph}',
style: t.labelLarge, style: t.labelLarge,

View file

@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import '../data/hub_endpoint.dart'; import '../data/hub_endpoint.dart';
import '../data/hub_repository.dart'; import '../data/hub_repository.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/hub_capabilities_card.dart'; import '../widgets/hub_capabilities_card.dart';
import '../widgets/lawheatmap_card.dart'; import '../widgets/reclaim_card.dart';
/// Hub connection + settings. Read-only summary at the top, then /// Hub connection + settings. Read-only summary at the top, then
/// an editable form for endpoint host/port/secure/token, then a /// an editable form for endpoint host/port/secure/token, then a
@ -109,18 +109,18 @@ class _HubStatusPageState extends State<HubStatusPage> {
final repo = widget.repository; final repo = widget.repository;
final s = _settings; final s = _settings;
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.all(LawHeatmapSpace.xl), padding: const EdgeInsets.all(ReclaimSpace.xl),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Ch∆In Hub', style: t.displaySmall), Text('Ch∆In Hub', style: t.displaySmall),
const SizedBox(height: LawHeatmapSpace.xs), const SizedBox(height: ReclaimSpace.xs),
Text( Text(
'Status der Verbindung und Konfiguration des lokalen Hubs.', 'Status der Verbindung und Konfiguration des lokalen Hubs.',
style: t.bodyLarge?.copyWith(color: LawHeatmapColors.mute), style: t.bodyLarge?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
accent: true, accent: true,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -142,23 +142,23 @@ class _HubStatusPageState extends State<HubStatusPage> {
], ],
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Verbindung konfigurieren', Text('Verbindung konfigurieren',
style: t.headlineSmall), style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'`chain serve` läuft standardmäßig auf ' '`chain serve` läuft standardmäßig auf '
'127.0.0.1:50051 ohne Auth. ' '127.0.0.1:50051 ohne Auth. '
'Bei aktiver Auth den Bearer-Token aus ' 'Bei aktiver Auth den Bearer-Token aus '
'~/.chain/hub-auth-token hier eintragen.', '~/.chain/hub-auth-token hier eintragen.',
style: t.labelSmall style: t.labelSmall
?.copyWith(color: LawHeatmapColors.mute), ?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
Row(children: [ Row(children: [
Expanded( Expanded(
flex: 3, flex: 3,
@ -170,7 +170,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
), ),
), ),
), ),
const SizedBox(width: LawHeatmapSpace.md), const SizedBox(width: ReclaimSpace.md),
Expanded( Expanded(
child: TextField( child: TextField(
controller: _portCtl, controller: _portCtl,
@ -182,7 +182,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
), ),
), ),
]), ]),
const SizedBox(height: LawHeatmapSpace.md), const SizedBox(height: ReclaimSpace.md),
TextField( TextField(
controller: _tokenCtl, controller: _tokenCtl,
decoration: const InputDecoration( decoration: const InputDecoration(
@ -190,23 +190,23 @@ class _HubStatusPageState extends State<HubStatusPage> {
), ),
obscureText: true, obscureText: true,
), ),
const SizedBox(height: LawHeatmapSpace.md), const SizedBox(height: ReclaimSpace.md),
Row(children: [ Row(children: [
Switch( Switch(
value: _secure, value: _secure,
onChanged: (v) => setState(() => _secure = v), onChanged: (v) => setState(() => _secure = v),
), ),
const SizedBox(width: LawHeatmapSpace.sm), const SizedBox(width: ReclaimSpace.sm),
const Text('TLS (https)'), const Text('TLS (https)'),
const SizedBox(width: LawHeatmapSpace.xl), const SizedBox(width: ReclaimSpace.xl),
Switch( Switch(
value: _useHub, value: _useHub,
onChanged: (v) => setState(() => _useHub = v), onChanged: (v) => setState(() => _useHub = v),
), ),
const SizedBox(width: LawHeatmapSpace.sm), const SizedBox(width: ReclaimSpace.sm),
const Text('Hub beim Start nutzen'), const Text('Hub beim Start nutzen'),
]), ]),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
Row(children: [ Row(children: [
FilledButton.icon( FilledButton.icon(
onPressed: _probing ? null : _probe, onPressed: _probing ? null : _probe,
@ -221,7 +221,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
: const Icon(Icons.bolt_outlined), : const Icon(Icons.bolt_outlined),
label: const Text('Verbindung testen'), label: const Text('Verbindung testen'),
), ),
const SizedBox(width: LawHeatmapSpace.md), const SizedBox(width: ReclaimSpace.md),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: _save, onPressed: _save,
icon: const Icon(Icons.save_outlined), icon: const Icon(Icons.save_outlined),
@ -229,24 +229,24 @@ class _HubStatusPageState extends State<HubStatusPage> {
), ),
]), ]),
if (_probeMessage != null) ...[ if (_probeMessage != null) ...[
const SizedBox(height: LawHeatmapSpace.md), const SizedBox(height: ReclaimSpace.md),
Text(_probeMessage!, Text(_probeMessage!,
style: t.labelLarge?.copyWith( style: t.labelLarge?.copyWith(
color: LawHeatmapColors.signal, color: ReclaimColors.signal,
)), )),
], ],
], ],
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
HubCapabilitiesCard(repository: repo), HubCapabilitiesCard(repository: repo),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
LawHeatmapCard( ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Geplanter Live-Flow', style: t.headlineSmall), Text('Geplanter Live-Flow', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'1. `chain serve` auf localhost:50051 starten.\n' '1. `chain serve` auf localhost:50051 starten.\n'
'2. Module installieren (`chain install`).\n' '2. Module installieren (`chain install`).\n'
@ -261,7 +261,7 @@ class _HubStatusPageState extends State<HubStatusPage> {
'Evaluation-Objekte übersetzt.', 'Evaluation-Objekte übersetzt.',
style: t.bodyLarge?.copyWith( style: t.bodyLarge?.copyWith(
height: 1.6, height: 1.6,
fontFamily: LawHeatmapTypography.mono, fontFamily: ReclaimTypography.mono,
fontSize: 12, fontSize: 12,
), ),
), ),
@ -282,12 +282,12 @@ class _HubStatusPageState extends State<HubStatusPage> {
width: 200, width: 200,
child: Text(label, child: Text(label,
style: t.labelLarge style: t.labelLarge
?.copyWith(color: LawHeatmapColors.mute)), ?.copyWith(color: ReclaimColors.mute)),
), ),
Expanded( Expanded(
child: Text(value, child: Text(value,
style: t.bodyLarge style: t.bodyLarge
?.copyWith(fontFamily: LawHeatmapTypography.mono)), ?.copyWith(fontFamily: ReclaimTypography.mono)),
), ),
], ],
), ),

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_theme.dart'; import '../theme/reclaim_theme.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/delta_mark.dart'; import '../widgets/delta_mark.dart';
import 'shell_page.dart'; import 'shell_page.dart';
@ -23,28 +23,28 @@ class LandingPage extends StatelessWidget {
constraints: const BoxConstraints(maxWidth: 720), constraints: const BoxConstraints(maxWidth: 720),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: LawHeatmapSpace.xxl, horizontal: ReclaimSpace.xxl,
vertical: LawHeatmapSpace.xxxl, vertical: ReclaimSpace.xxxl,
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const DeltaMark(size: 96), const DeltaMark(size: 96),
const SizedBox(height: LawHeatmapSpace.xxl), const SizedBox(height: ReclaimSpace.xxl),
Text('F∆I Law-Heatmap', style: t.displaySmall), Text('Recl∆Im', style: t.displaySmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — ' 'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — '
'auf Ch∆In, mit Quellen-Audit by construction.', 'auf Ch∆In, mit Quellen-Audit by construction.',
style: t.bodyLarge style: t.bodyLarge
?.copyWith(color: LawHeatmapColors.mute), ?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: LawHeatmapSpace.xxl), const SizedBox(height: ReclaimSpace.xxl),
Wrap( Wrap(
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
spacing: LawHeatmapSpace.md, spacing: ReclaimSpace.md,
runSpacing: LawHeatmapSpace.sm, runSpacing: ReclaimSpace.sm,
children: [ children: [
FilledButton.icon( FilledButton.icon(
onPressed: () { onPressed: () {
@ -61,18 +61,18 @@ class LandingPage extends StatelessWidget {
Text( Text(
'Demo: 5 Pilot-Normen, ohne Hub-Verbindung', 'Demo: 5 Pilot-Normen, ohne Hub-Verbindung',
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
], ],
), ),
const SizedBox(height: LawHeatmapSpace.xl), const SizedBox(height: ReclaimSpace.xl),
Text( Text(
'v0.1.0 · Phase 0 · Mock-Modus · ' 'v0.1.0 · Phase 0 · Mock-Modus · '
'Live-Hub-Anbindung folgt in Woche 1', 'Live-Hub-Anbindung folgt in Woche 1',
style: LawHeatmapTheme.mono( style: ReclaimTheme.mono(
t.labelSmall ?? const TextStyle(), t.labelSmall ?? const TextStyle(),
).copyWith(color: LawHeatmapColors.mute), ).copyWith(color: ReclaimColors.mute),
), ),
], ],
), ),

View file

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/demo_banner.dart'; import '../widgets/demo_banner.dart';
import '../widgets/lawheatmap_card.dart'; import '../widgets/reclaim_card.dart';
import '../widgets/tier_badge.dart'; import '../widgets/tier_badge.dart';
import 'norm_detail_page.dart'; import 'norm_detail_page.dart';
@ -21,20 +21,20 @@ class NormsListPage extends StatelessWidget {
const DemoBanner(), const DemoBanner(),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(LawHeatmapSpace.xl), padding: const EdgeInsets.all(ReclaimSpace.xl),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Normen-Pilotkorpus', style: t.displaySmall), Text('Normen-Pilotkorpus', style: t.displaySmall),
const SizedBox(height: LawHeatmapSpace.xs), const SizedBox(height: ReclaimSpace.xs),
Text( Text(
'5 Normen für den vertikalen Durchstich. ' '5 Normen für den vertikalen Durchstich. '
'Korpus erweiterbar in Phase 1.', 'Korpus erweiterbar in Phase 1.',
style: t.bodyLarge?.copyWith( style: t.bodyLarge?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
FutureBuilder<List<Evaluation>>( FutureBuilder<List<Evaluation>>(
future: repository.list(), future: repository.list(),
builder: (context, snap) { builder: (context, snap) {
@ -47,7 +47,7 @@ class NormsListPage extends StatelessWidget {
for (final e in snap.data!) for (final e in snap.data!)
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: LawHeatmapSpace.md), bottom: ReclaimSpace.md),
child: _NormRow( child: _NormRow(
evaluation: e, evaluation: e,
repository: repository, repository: repository,
@ -86,7 +86,7 @@ class _NormRow extends StatelessWidget {
), ),
); );
}, },
child: LawHeatmapCard( child: ReclaimCard(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -103,14 +103,14 @@ class _NormRow extends StatelessWidget {
Text( Text(
evaluation.norm.eli, evaluation.norm.eli,
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
fontFamily: LawHeatmapTypography.mono, fontFamily: ReclaimTypography.mono,
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: LawHeatmapSpace.lg), const SizedBox(width: ReclaimSpace.lg),
Expanded( Expanded(
child: _MetricColumn( child: _MetricColumn(
label: 'Schaden / Jahr', label: 'Schaden / Jahr',
@ -130,7 +130,7 @@ class _NormRow extends StatelessWidget {
value: _kmu(evaluation.affectedCount), value: _kmu(evaluation.affectedCount),
), ),
), ),
const SizedBox(width: LawHeatmapSpace.md), const SizedBox(width: ReclaimSpace.md),
TierBadge(tier: evaluation.tierLowest), TierBadge(tier: evaluation.tierLowest),
], ],
), ),
@ -166,7 +166,7 @@ class _MetricColumn extends StatelessWidget {
children: [ children: [
Text( Text(
label, label,
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute), style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(value, style: t.titleMedium), Text(value, style: t.titleMedium),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import '../widgets/delta_mark.dart'; import '../widgets/delta_mark.dart';
import 'about_page.dart'; import 'about_page.dart';
import 'heatmap_page.dart'; import 'heatmap_page.dart';
@ -42,18 +42,18 @@ class _ShellPageState extends State<ShellPage> {
onDestinationSelected: (i) => setState(() => _index = i), onDestinationSelected: (i) => setState(() => _index = i),
leading: Padding( leading: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: LawHeatmapSpace.lg, vertical: ReclaimSpace.lg,
horizontal: LawHeatmapSpace.md, horizontal: ReclaimSpace.md,
), ),
child: Column( child: Column(
children: [ children: [
const DeltaMark(size: 36), const DeltaMark(size: 36),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'F∆I', 'F∆I',
style: t.labelLarge?.copyWith( style: t.labelLarge?.copyWith(
letterSpacing: 2, letterSpacing: 2,
color: LawHeatmapColors.signal, color: ReclaimColors.signal,
), ),
), ),
], ],

View file

@ -1,35 +1,35 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'lawheatmap_tokens.dart'; import 'reclaim_tokens.dart';
/// ThemeData assembly for the FI Law-Heatmap client. /// ThemeData assembly for the FI ReclIm client.
/// ///
/// Dark mode is the default (consistent with fai_web's /// Dark mode is the default (consistent with fai_web's
/// `color-scheme: dark`). Light mode falls back to the paper /// `color-scheme: dark`). Light mode falls back to the paper
/// palette for printing/screenshot work. /// palette for printing/screenshot work.
class LawHeatmapTheme { class ReclaimTheme {
LawHeatmapTheme._(); ReclaimTheme._();
static ThemeData dark() => _build(Brightness.dark); static ThemeData dark() => _build(Brightness.dark);
static ThemeData light() => _build(Brightness.light); static ThemeData light() => _build(Brightness.light);
static ThemeData _build(Brightness brightness) { static ThemeData _build(Brightness brightness) {
final isDark = brightness == Brightness.dark; final isDark = brightness == Brightness.dark;
final canvas = isDark ? LawHeatmapColors.ink : LawHeatmapColors.paper; final canvas = isDark ? ReclaimColors.ink : ReclaimColors.paper;
final surface = final surface =
isDark ? LawHeatmapColors.inkRaised : LawHeatmapColors.paperRaised; isDark ? ReclaimColors.inkRaised : ReclaimColors.paperRaised;
final onSurface = isDark final onSurface = isDark
? const Color(0xFFE9E7E2) ? const Color(0xFFE9E7E2)
: const Color(0xFF1A1B1E); : const Color(0xFF1A1B1E);
final colorScheme = ColorScheme( final colorScheme = ColorScheme(
brightness: brightness, brightness: brightness,
primary: LawHeatmapColors.signal, primary: ReclaimColors.signal,
onPrimary: LawHeatmapColors.ink, onPrimary: ReclaimColors.ink,
secondary: LawHeatmapColors.mute, secondary: ReclaimColors.mute,
onSecondary: onSurface, onSecondary: onSurface,
error: const Color(0xFFD8723A), error: const Color(0xFFD8723A),
onError: LawHeatmapColors.ink, onError: ReclaimColors.ink,
surface: surface, surface: surface,
onSurface: onSurface, onSurface: onSurface,
); );
@ -42,33 +42,33 @@ class LawHeatmapTheme {
colorScheme: colorScheme, colorScheme: colorScheme,
scaffoldBackgroundColor: canvas, scaffoldBackgroundColor: canvas,
canvasColor: canvas, canvasColor: canvas,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
textTheme: textTheme, textTheme: textTheme,
cardTheme: CardThemeData( cardTheme: CardThemeData(
color: surface, color: surface,
elevation: 0, elevation: 0,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(LawHeatmapRadius.md), borderRadius: BorderRadius.all(ReclaimRadius.md),
side: BorderSide(color: Color(0x24FFFFFF)), side: BorderSide(color: Color(0x24FFFFFF)),
), ),
), ),
dividerTheme: DividerThemeData( dividerTheme: DividerThemeData(
color: onSurface.withValues(alpha: LawHeatmapColors.hairlineDarkOpacity), color: onSurface.withValues(alpha: ReclaimColors.hairlineDarkOpacity),
thickness: 1, thickness: 1,
space: 1, space: 1,
), ),
filledButtonTheme: FilledButtonThemeData( filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
backgroundColor: LawHeatmapColors.signal, backgroundColor: ReclaimColors.signal,
foregroundColor: LawHeatmapColors.ink, foregroundColor: ReclaimColors.ink,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(LawHeatmapRadius.sm), borderRadius: BorderRadius.all(ReclaimRadius.sm),
), ),
textStyle: textTheme.labelLarge, textStyle: textTheme.labelLarge,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: LawHeatmapSpace.lg, horizontal: ReclaimSpace.lg,
vertical: LawHeatmapSpace.md, vertical: ReclaimSpace.md,
), ),
), ),
), ),
@ -76,8 +76,8 @@ class LawHeatmapTheme {
} }
static TextTheme _textTheme(Color onSurface) { static TextTheme _textTheme(Color onSurface) {
const display = LawHeatmapTypography.display; const display = ReclaimTypography.display;
const body = LawHeatmapTypography.body; const body = ReclaimTypography.body;
return TextTheme( return TextTheme(
displaySmall: TextStyle( displaySmall: TextStyle(
fontFamily: display, fontFamily: display,
@ -123,5 +123,5 @@ class LawHeatmapTheme {
/// Convenience: JetBrains-Mono variant of an existing style for /// Convenience: JetBrains-Mono variant of an existing style for
/// technical content (ELI/CELEX, SHA-256, audit-event IDs). /// technical content (ELI/CELEX, SHA-256, audit-event IDs).
static TextStyle mono(TextStyle base) => static TextStyle mono(TextStyle base) =>
base.copyWith(fontFamily: LawHeatmapTypography.mono); base.copyWith(fontFamily: ReclaimTypography.mono);
} }

View file

@ -1,14 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// Visual tokens for the FI Law-Heatmap client, sourced from the /// Visual tokens for the FI ReclIm client, sourced from the
/// fai_web brand palette (see fai_web/src/styles/global.css:14-20). /// fai_web brand palette (see fai_web/src/styles/global.css:14-20).
/// ///
/// Unlike `fai_chain_studio`'s `ChainColors` (Tailwind sky-400 — /// Unlike `fai_chain_studio`'s `ChainColors` (Tailwind sky-400 —
/// dev-tool look), this palette matches the public-facing brand: /// dev-tool look), this palette matches the public-facing brand:
/// ink/paper with a petrol "signal" accent that doubles as the eye /// ink/paper with a petrol "signal" accent that doubles as the eye
/// of the `` mark. /// of the `` mark.
class LawHeatmapColors { class ReclaimColors {
LawHeatmapColors._(); ReclaimColors._();
// Core palette (fai_web/src/styles/global.css) // Core palette (fai_web/src/styles/global.css)
static const ink = Color(0xFF08090A); static const ink = Color(0xFF08090A);
@ -40,8 +40,8 @@ class LawHeatmapColors {
/// Spacing scale 4/8/12/16/24/32/48 multiples, matching /// Spacing scale 4/8/12/16/24/32/48 multiples, matching
/// `ChainSpace` from `fai_chain_studio/lib/theme/tokens.dart`. /// `ChainSpace` from `fai_chain_studio/lib/theme/tokens.dart`.
class LawHeatmapSpace { class ReclaimSpace {
LawHeatmapSpace._(); ReclaimSpace._();
static const xs = 4.0; static const xs = 4.0;
static const sm = 8.0; static const sm = 8.0;
@ -52,15 +52,15 @@ class LawHeatmapSpace {
static const xxxl = 48.0; static const xxxl = 48.0;
} }
class LawHeatmapRadius { class ReclaimRadius {
LawHeatmapRadius._(); ReclaimRadius._();
static const sm = Radius.circular(6); static const sm = Radius.circular(6);
static const md = Radius.circular(10); static const md = Radius.circular(10);
} }
class LawHeatmapTypography { class ReclaimTypography {
LawHeatmapTypography._(); ReclaimTypography._();
static const display = 'SpaceGroteskVariable'; static const display = 'SpaceGroteskVariable';
static const body = 'InterVariable'; static const body = 'InterVariable';

View file

@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
/// The FI / ChIn brand mark: an upward triangle (the ``) with /// The FI / ChIn brand mark: an upward triangle (the ``) with
/// a petrol "eye" at the centre. Geometry mirrors fai_web's /// a petrol "eye" at the centre. Geometry mirrors fai_web's
@ -89,7 +89,7 @@ class _DeltaPainter extends CustomPainter {
// Eye petrol fill, centred on the triangle's centroid. // Eye petrol fill, centred on the triangle's centroid.
final eyeCentre = Offset(w / 2, inset + (h - 2 * inset) * 2 / 3); final eyeCentre = Offset(w / 2, inset + (h - 2 * inset) * 2 / 3);
final eyeR = w * 0.06 * pulse; final eyeR = w * 0.06 * pulse;
final eye = Paint()..color = LawHeatmapColors.signal; final eye = Paint()..color = ReclaimColors.signal;
canvas.drawCircle(eyeCentre, eyeR, eye); canvas.drawCircle(eyeCentre, eyeR, eye);
} }

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
/// Permanent banner shown above all data screens in mock mode. /// Permanent banner shown above all data screens in mock mode.
/// Carries the political-defensibility caveat: this is demo data, /// Carries the political-defensibility caveat: this is demo data,
@ -14,29 +14,29 @@ class DemoBanner extends StatelessWidget {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: LawHeatmapSpace.lg, horizontal: ReclaimSpace.lg,
vertical: LawHeatmapSpace.sm, vertical: ReclaimSpace.sm,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: LawHeatmapColors.tierT4.withValues(alpha: 0.12), color: ReclaimColors.tierT4.withValues(alpha: 0.12),
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: LawHeatmapColors.tierT4.withValues(alpha: 0.55), color: ReclaimColors.tierT4.withValues(alpha: 0.55),
), ),
), ),
), ),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.science_outlined, const Icon(Icons.science_outlined,
size: 18, color: LawHeatmapColors.tierT4), size: 18, color: ReclaimColors.tierT4),
const SizedBox(width: LawHeatmapSpace.sm), const SizedBox(width: ReclaimSpace.sm),
Expanded( Expanded(
child: Text( child: Text(
'Demo-Daten — Phase 0, ohne Verbands-Erhebung. ' 'Demo-Daten — Phase 0, ohne Verbands-Erhebung. '
'Keine Juristen-Approval, keine Beirats-Validierung. ' 'Keine Juristen-Approval, keine Beirats-Validierung. '
'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.', 'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.',
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.tierT4, color: ReclaimColors.tierT4,
), ),
), ),
), ),

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import 'lawheatmap_card.dart'; import 'reclaim_card.dart';
import 'source_chip.dart'; import 'source_chip.dart';
/// Sidebar listing the citable sources behind an evaluation. /// Sidebar listing the citable sources behind an evaluation.
@ -16,39 +16,39 @@ class EvidenceSidebar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Theme.of(context).textTheme; final t = Theme.of(context).textTheme;
return LawHeatmapCard( return ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Quellen', style: t.headlineSmall), Text('Quellen', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'Jede Zahl trägt die Tier-Stufe ihres schwächsten ' 'Jede Zahl trägt die Tier-Stufe ihres schwächsten '
'Bestandteils (Regel der niedrigsten Stufe, §6.7).', 'Bestandteils (Regel der niedrigsten Stufe, §6.7).',
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute), style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
), ),
const SizedBox(height: LawHeatmapSpace.lg), const SizedBox(height: ReclaimSpace.lg),
for (final s in evaluation.sources) SourceChip(source: s), for (final s in evaluation.sources) SourceChip(source: s),
const Divider(height: LawHeatmapSpace.xl), const Divider(height: ReclaimSpace.xl),
Text('Audit-Event', style: t.labelLarge), Text('Audit-Event', style: t.labelLarge),
const SizedBox(height: 4), const SizedBox(height: 4),
SelectableText( SelectableText(
evaluation.auditEventId, evaluation.auditEventId,
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
fontFamily: LawHeatmapTypography.mono, fontFamily: ReclaimTypography.mono,
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'Quell-SHA-256:', 'Quell-SHA-256:',
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute), style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
), ),
SelectableText( SelectableText(
evaluation.norm.sourceSha256, evaluation.norm.sourceSha256,
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
fontFamily: LawHeatmapTypography.mono, fontFamily: ReclaimTypography.mono,
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
fontSize: 10, fontSize: 10,
), ),
), ),

View file

@ -3,7 +3,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
/// 2D scatter heatmap x: Schaden (/Jahr, log-scaled), /// 2D scatter heatmap x: Schaden (/Jahr, log-scaled),
/// y: Nutzen (05), point area: Betroffenheit (sqrt-scaled). /// y: Nutzen (05), point area: Betroffenheit (sqrt-scaled).
@ -121,7 +121,7 @@ class _HeatmapPainter extends CustomPainter {
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final layout = _Layout(size); final layout = _Layout(size);
final onSurface = theme.colorScheme.onSurface; final onSurface = theme.colorScheme.onSurface;
const mute = LawHeatmapColors.mute; const mute = ReclaimColors.mute;
// Plot frame. // Plot frame.
final frame = Paint() final frame = Paint()
@ -163,7 +163,7 @@ class _HeatmapPainter extends CustomPainter {
const axisStyle = TextStyle( const axisStyle = TextStyle(
color: mute, color: mute,
fontSize: 10, fontSize: 10,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
); );
_drawText( _drawText(
canvas, canvas,
@ -205,8 +205,8 @@ class _HeatmapPainter extends CustomPainter {
final r = layout.radius(e); final r = layout.radius(e);
final hot = e.skmEurPerYear > 50000000; final hot = e.skmEurPerYear > 50000000;
final color = Color.lerp( final color = Color.lerp(
LawHeatmapColors.benefitSaturated, ReclaimColors.benefitSaturated,
LawHeatmapColors.harmWarm, ReclaimColors.harmWarm,
hot ? 0.5 : 0.0, hot ? 0.5 : 0.0,
)!; )!;
final body = Paint()..color = color.withValues(alpha: 0.7); final body = Paint()..color = color.withValues(alpha: 0.7);
@ -224,7 +224,7 @@ class _HeatmapPainter extends CustomPainter {
TextStyle( TextStyle(
color: onSurface, color: onSurface,
fontSize: 11, fontSize: 11,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
fontWeight: i == hoverIndex ? FontWeight.w600 : FontWeight.w400, fontWeight: i == hoverIndex ? FontWeight.w600 : FontWeight.w400,
), ),
); );
@ -240,8 +240,8 @@ class _HeatmapPainter extends CustomPainter {
..color = theme.colorScheme.surface.withValues(alpha: 0.92); ..color = theme.colorScheme.surface.withValues(alpha: 0.92);
final boxBorder = Paint() final boxBorder = Paint()
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..color = LawHeatmapColors.signal.withValues(alpha: 0.6); ..color = ReclaimColors.signal.withValues(alpha: 0.6);
final rrect = RRect.fromRectAndRadius(box, LawHeatmapRadius.sm); final rrect = RRect.fromRectAndRadius(box, ReclaimRadius.sm);
canvas.drawRRect(rrect, boxPaint); canvas.drawRRect(rrect, boxPaint);
canvas.drawRRect(rrect, boxBorder); canvas.drawRRect(rrect, boxBorder);
_drawText( _drawText(
@ -252,7 +252,7 @@ class _HeatmapPainter extends CustomPainter {
color: onSurface, color: onSurface,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
), ),
); );
_drawText( _drawText(
@ -262,7 +262,7 @@ class _HeatmapPainter extends CustomPainter {
const TextStyle( const TextStyle(
color: mute, color: mute,
fontSize: 11, fontSize: 11,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
), ),
); );
_drawText( _drawText(
@ -272,7 +272,7 @@ class _HeatmapPainter extends CustomPainter {
const TextStyle( const TextStyle(
color: mute, color: mute,
fontSize: 11, fontSize: 11,
fontFamily: LawHeatmapTypography.body, fontFamily: ReclaimTypography.body,
), ),
); );
} }

View file

@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import '../data/hub_repository.dart'; import '../data/hub_repository.dart';
import '../data/repository.dart'; import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import 'lawheatmap_card.dart'; import 'reclaim_card.dart';
/// Lists the capabilities the connected hub has installed. /// Lists the capabilities the connected hub has installed.
/// Empty / explanatory state when the active repository is not /// Empty / explanatory state when the active repository is not
@ -39,7 +39,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
final repo = widget.repository; final repo = widget.repository;
final liveHub = repo is HubRepository && repo.isHealthy; final liveHub = repo is HubRepository && repo.isHealthy;
return LawHeatmapCard( return ReclaimCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -53,13 +53,13 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
), ),
]), ]),
const SizedBox(height: LawHeatmapSpace.sm), const SizedBox(height: ReclaimSpace.sm),
if (!liveHub) if (!liveHub)
Text( Text(
'Sichtbar sobald die App im Hub-Modus startet und ' 'Sichtbar sobald die App im Hub-Modus startet und '
'der Hub erreichbar ist (siehe Verbindung oben).', 'der Hub erreichbar ist (siehe Verbindung oben).',
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
) )
else else
@ -68,14 +68,14 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
builder: (context, snap) { builder: (context, snap) {
if (snap.connectionState == ConnectionState.waiting) { if (snap.connectionState == ConnectionState.waiting) {
return const Padding( return const Padding(
padding: EdgeInsets.all(LawHeatmapSpace.md), padding: EdgeInsets.all(ReclaimSpace.md),
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
} }
if (snap.hasError) { if (snap.hasError) {
return Text('Fehler: ${snap.error}', return Text('Fehler: ${snap.error}',
style: t.labelLarge style: t.labelLarge
?.copyWith(color: LawHeatmapColors.harmWarm)); ?.copyWith(color: ReclaimColors.harmWarm));
} }
final items = snap.data ?? const []; final items = snap.data ?? const [];
if (items.isEmpty) { if (items.isEmpty) {
@ -83,7 +83,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
'Hub hat noch keine Capabilities installiert. ' 'Hub hat noch keine Capabilities installiert. '
'Mit `chain install <name>` hinzufügen.', 'Mit `chain install <name>` hinzufügen.',
style: t.bodyLarge?.copyWith( style: t.bodyLarge?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
); );
} }
@ -101,7 +101,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
child: Text( child: Text(
'${c.capability}@${c.version}', '${c.capability}@${c.version}',
style: t.bodyLarge?.copyWith( style: t.bodyLarge?.copyWith(
fontFamily: LawHeatmapTypography.mono, fontFamily: ReclaimTypography.mono,
), ),
), ),
), ),
@ -110,7 +110,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
'${c.moduleName} ${c.moduleVersion}' '${c.moduleName} ${c.moduleVersion}'
'${c.kind.isEmpty ? "" : " · ${c.kind}"}', '${c.kind.isEmpty ? "" : " · ${c.kind}"}',
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
), ),

View file

@ -1,15 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
/// Surface container visual equivalent of fai_chain_studio's /// Surface container visual equivalent of fai_chain_studio's
/// `ChainCard`, retuned to the fai_web palette. /// `ChainCard`, retuned to the fai_web palette.
class LawHeatmapCard extends StatelessWidget { class ReclaimCard extends StatelessWidget {
const LawHeatmapCard({ const ReclaimCard({
super.key, super.key,
required this.child, required this.child,
this.padding = this.padding =
const EdgeInsets.all(LawHeatmapSpace.lg), const EdgeInsets.all(ReclaimSpace.lg),
this.accent = false, this.accent = false,
this.expand = false, this.expand = false,
}); });
@ -39,9 +39,9 @@ class LawHeatmapCard extends StatelessWidget {
Container( Container(
height: 2, height: 2,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: LawHeatmapColors.signal, color: ReclaimColors.signal,
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: LawHeatmapRadius.md, top: ReclaimRadius.md,
), ),
), ),
), ),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
import 'tier_badge.dart'; import 'tier_badge.dart';
/// A citable source line: tier badge + label + (planned) link. /// A citable source line: tier badge + label + (planned) link.
@ -16,7 +16,7 @@ class SourceChip extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Theme.of(context).textTheme; final t = Theme.of(context).textTheme;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.sm), padding: const EdgeInsets.only(bottom: ReclaimSpace.sm),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -24,7 +24,7 @@ class SourceChip extends StatelessWidget {
padding: const EdgeInsets.only(top: 2), padding: const EdgeInsets.only(top: 2),
child: TierBadge(tier: source.tier, compact: true), child: TierBadge(tier: source.tier, compact: true),
), ),
const SizedBox(width: LawHeatmapSpace.sm), const SizedBox(width: ReclaimSpace.sm),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -33,14 +33,14 @@ class SourceChip extends StatelessWidget {
Text( Text(
source.url, source.url,
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
), ),
), ),
if (source.note != null) if (source.note != null)
Text( Text(
source.note!, source.note!,
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute, color: ReclaimColors.mute,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../data/models.dart'; import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart'; import '../theme/reclaim_tokens.dart';
/// Tier badge surfaces the lowest evidence tier in a composed /// Tier badge surfaces the lowest evidence tier in a composed
/// value. Every figure in the app carries one of these next to it. /// value. Every figure in the app carries one of these next to it.
@ -15,22 +15,22 @@ class TierBadge extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Theme.of(context).textTheme; final t = Theme.of(context).textTheme;
final color = switch (tier) { final color = switch (tier) {
EvidenceTier.t1 => LawHeatmapColors.tierT1, EvidenceTier.t1 => ReclaimColors.tierT1,
EvidenceTier.t2 => LawHeatmapColors.tierT2, EvidenceTier.t2 => ReclaimColors.tierT2,
EvidenceTier.t3 => LawHeatmapColors.tierT3, EvidenceTier.t3 => ReclaimColors.tierT3,
EvidenceTier.t4 => LawHeatmapColors.tierT4, EvidenceTier.t4 => ReclaimColors.tierT4,
}; };
return Tooltip( return Tooltip(
message: '${tier.short}${tier.description}', message: '${tier.short}${tier.description}',
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: compact ? 6 : LawHeatmapSpace.sm, horizontal: compact ? 6 : ReclaimSpace.sm,
vertical: compact ? 2 : 4, vertical: compact ? 2 : 4,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color.withValues(alpha: 0.18), color: color.withValues(alpha: 0.18),
border: Border.all(color: color.withValues(alpha: 0.55)), border: Border.all(color: color.withValues(alpha: 0.55)),
borderRadius: const BorderRadius.all(LawHeatmapRadius.sm), borderRadius: const BorderRadius.all(ReclaimRadius.sm),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View file

@ -65,7 +65,7 @@
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* lawheatmap_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "lawheatmap_app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* reclaim_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "reclaim_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -134,7 +134,7 @@
33CC10EE2044A3C60003C045 /* Products */ = { 33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
33CC10ED2044A3C60003C045 /* lawheatmap_app.app */, 33CC10ED2044A3C60003C045 /* reclaim_app.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
); );
name = Products; name = Products;
@ -224,7 +224,7 @@
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
); );
productName = Runner; productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* lawheatmap_app.app */; productReference = 33CC10ED2044A3C60003C045 /* reclaim_app.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -395,10 +395,10 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
}; };
name = Debug; name = Debug;
}; };
@ -409,10 +409,10 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
}; };
name = Release; name = Release;
}; };
@ -423,10 +423,10 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/lawheatmap_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/lawheatmap_app"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reclaim_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/reclaim_app";
}; };
name = Profile; name = Profile;
}; };

View file

@ -15,7 +15,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lawheatmap_app.app" BuildableName = "reclaim_app.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -33,7 +33,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lawheatmap_app.app" BuildableName = "reclaim_app.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -49,7 +49,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lawheatmap_app.app" BuildableName = "reclaim_app.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -84,7 +84,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lawheatmap_app.app" BuildableName = "reclaim_app.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -101,7 +101,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "lawheatmap_app.app" BuildableName = "reclaim_app.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>

View file

@ -5,10 +5,10 @@
// 'flutter create' template. // 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window. // The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = lawheatmap_app PRODUCT_NAME = reclaim_app
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.lawheatmapApp PRODUCT_BUNDLE_IDENTIFIER = ai.flemming.reclaim
// The copyright displayed in application information // The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 ai.flemming. All rights reserved. PRODUCT_COPYRIGHT = Copyright © 2026 ai.flemming. All rights reserved.

View file

@ -409,6 +409,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
url: "https://pub.dev"
source: hosted
version: "6.3.32"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
url: "https://pub.dev"
source: hosted
version: "3.2.5"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View file

@ -1,5 +1,5 @@
name: lawheatmap_app name: reclaim_app
description: F∆I Law-Heatmap — Flutter client for the Ch∆In-based law-bureaucracy heatmap. description: Recl∆Im — Flutter client for the Ch∆In-based duty-level reform-mapping platform (F∆I).
publish_to: 'none' publish_to: 'none'
version: 0.1.0+1 version: 0.1.0+1
@ -14,6 +14,7 @@ dependencies:
sdk: flutter sdk: flutter
intl: ^0.20.2 intl: ^0.20.2
shared_preferences: ^2.3.0 shared_preferences: ^2.3.0
url_launcher: ^6.3.0
# Ch∆In gRPC wire — sibling-path dep, same pattern as # Ch∆In gRPC wire — sibling-path dep, same pattern as
# fai_chain_studio. Adapt the chain_client_sdk_dart relative # fai_chain_studio. Adapt the chain_client_sdk_dart relative

View file

@ -4,7 +4,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:lawheatmap_app/main.dart'; import 'package:reclaim_app/main.dart';
void main() { void main() {
// Generous surface so NavigationRail + Heatmap fit without // Generous surface so NavigationRail + Heatmap fit without
@ -18,9 +18,9 @@ void main() {
testWidgets('Landing renders brand mark + CTA', testWidgets('Landing renders brand mark + CTA',
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.binding.setSurfaceSize(surfaceSize); await tester.binding.setSurfaceSize(surfaceSize);
await tester.pumpWidget(const LawHeatmapApp()); await tester.pumpWidget(const ReclaimApp());
await tester.pump(); await tester.pump();
expect(find.text('F∆I Law-Heatmap'), findsOneWidget); expect(find.text('Recl∆Im'), findsOneWidget);
expect( expect(
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'), find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
findsOneWidget, findsOneWidget,
@ -31,7 +31,7 @@ void main() {
testWidgets('Tapping CTA reaches the shell and shows the heatmap', testWidgets('Tapping CTA reaches the shell and shows the heatmap',
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.binding.setSurfaceSize(surfaceSize); await tester.binding.setSurfaceSize(surfaceSize);
await tester.pumpWidget(const LawHeatmapApp()); await tester.pumpWidget(const ReclaimApp());
await tester.pump(); await tester.pump();
await tester.tap( await tester.tap(
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'), find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),

View file

@ -122,7 +122,7 @@ steps:
- id: review - id: review
use: system.approval@^0 use: system.approval@^0
with: with:
title: "F∆I Law-Heatmap — Review ${{ normalize.norm.eli }}" title: "Recl∆Im (F∆I) — Review ${{ normalize.norm.eli }}"
payload: payload:
norm: ${{ normalize.norm }} norm: ${{ normalize.norm }}
skm: ${{ skm.report }} skm: ${{ skm.report }}