Compare commits

..

No commits in common. "ae9fc45177f483894628a0e4ca6de7c2f86a1a2f" and "94f44736869b3c72c36ea535bb93ce7c68e5d2b7" have entirely different histories.

7 changed files with 28 additions and 177 deletions

View file

@ -90,12 +90,6 @@ class Fixtures {
'T (Tarif) T1 — DESTATIS Verdiensterhebung kaufm. Tätigkeit ~30 €/h; '
'h (Zeitaufwand) T3 — eigene Schätzung ~30 min, NKR-Validierung ausstehend. '
'Regel der niedrigsten Stufe: Gesamt → T3 (in der Demo konservativ als T4 markiert).',
componentSummary: const [
'P · T1 — DESTATIS Gewerbeanzeigen',
'F · T1 — anlassbezogen, je Vorgang',
'T · T1 — DESTATIS Verdiensterhebung',
'h · T3 — eigene Schätzung ~30 min',
],
);
static final _kassenSichV = Evaluation(
@ -183,12 +177,6 @@ class Fixtures {
'Laufende Folgekosten (TSE-Ausfallmeldungen, Hardware-Erneuerung): T2 '
'aus DIHK-Bürokratie-Index + DEHOGA-Gastronomie-Stichprobe. '
'Hoher Frust-Score wegen Pluralität TSE-Hersteller × Finanzamt-Vollzug.',
componentSummary: const [
'P · T1 — Kassen-pflichtige Betriebe',
'F · T1 — kontinuierlich + Ausfälle',
'T · T2 — DIHK Bürokratie-Index',
'h · T2 — DEHOGA-Stichprobe',
],
);
static final _dsgvoArt30 = Evaluation(
@ -284,13 +272,6 @@ class Fixtures {
'pro Unternehmen liegt im Streit zwischen BfDI-Position (gering, weil '
'<250-MA-Ausnahme) und DIHK-Position (hoch, weil 5-Abs.-Ausnahme '
'praktisch nicht greift). Tier T2 für Magnitude.',
componentSummary: const [
'P · T1 — DESTATIS Unternehmensregister',
'F · T1 — jährlich überprüfen',
'T · T2 — DIHK-Position',
'h · T2 — Streit BfDI ↔ DIHK',
'EU-determiniert · Reform-Hebel Brüssel',
],
);
static final _berlinBauO60 = Evaluation(
@ -370,13 +351,6 @@ class Fixtures {
'→ Genehmigung sind im Land getrennt geregelt). Höchster Frust-Score '
'aus Verweistiefe + Behörden-Pluralität (Bezirksamt × SenStadt × '
'Beteiligungsverfahren).',
componentSummary: const [
'P · T2 — Handwerkskammer Berlin',
'F · T1 — pro Bauvorhaben',
'T · T2 — Bauwirtschaft Berlin',
'h · T3 — Verfahren mehrstufig',
'Frust · T2 — Bezirk × Senat × Beteiligung',
],
);
static final _miLoG17 = Evaluation(
@ -454,12 +428,5 @@ class Fixtures {
'dokumentierte den initial-Erfüllungsaufwand; aktuelle Zahlen '
'aus ZDH Bürokratiekosten-Handwerk. Bürokratieentlastungs-'
'gesetz IV adressierte den § 17 — Reform-Bewegung ist im Lauf.',
componentSummary: const [
'P · T1 — DESTATIS MiLoG-pflichtig',
'F · T1 — arbeitstäglich',
'T · T1 — DESTATIS Tariflohn',
'h · T1 — NKR-Stellungnahme 2014',
'Reform · BEG IV in Lauf',
],
);
}

View file

@ -142,7 +142,6 @@ class Evaluation {
required this.sources,
required this.auditEventId,
this.notes,
this.componentSummary = const [],
});
final Norm norm;
@ -173,10 +172,4 @@ class Evaluation {
final String auditEventId;
final String? notes;
/// Compact lines for at-a-glance reading on list views.
/// One line per SKM-Komponente (P / F / T / h) plus optional
/// extras each carries its tier-tag inline. Empty list
/// fall back to the notes field for the list view.
final List<String> componentSummary;
}

View file

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import '../data/repository.dart';
import '../theme/reclaim_theme.dart';
import '../theme/reclaim_tokens.dart';
import '../widgets/brand_wordmark.dart';
import '../widgets/delta_mark.dart';
import 'shell_page.dart';
@ -21,7 +20,7 @@ class LandingPage extends StatelessWidget {
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 820),
constraints: const BoxConstraints(maxWidth: 720),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: ReclaimSpace.xxl,
@ -31,18 +30,9 @@ class LandingPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DeltaMark(size: 96),
SizedBox(width: ReclaimSpace.xl),
BrandWordmark(
size: 56,
letterSpacing: -1.0,
),
],
),
const SizedBox(height: ReclaimSpace.xl),
const DeltaMark(size: 96),
const SizedBox(height: ReclaimSpace.xxl),
Text('Recl∆Im', style: t.displaySmall),
const SizedBox(height: ReclaimSpace.sm),
Text(
'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — '

View file

@ -107,42 +107,31 @@ class _NormRow extends StatelessWidget {
color: ReclaimColors.mute,
),
),
const SizedBox(height: ReclaimSpace.sm),
TierBadge(tier: evaluation.tierLowest),
],
),
),
const SizedBox(width: ReclaimSpace.lg),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_MetricColumn(
child: _MetricColumn(
label: 'Schaden / Jahr',
value: _eur(evaluation.skmEurPerYear),
),
const SizedBox(height: ReclaimSpace.sm),
_MetricColumn(
),
Expanded(
child: _MetricColumn(
label: 'Nutzen',
value:
'${evaluation.benefitScore.toStringAsFixed(1)} / 5',
),
const SizedBox(height: ReclaimSpace.sm),
_MetricColumn(
),
Expanded(
child: _MetricColumn(
label: 'Betroffene',
value: _kmu(evaluation.affectedCount),
),
],
),
),
const SizedBox(width: ReclaimSpace.lg),
Expanded(
flex: 3,
child: _ComponentSummary(
summary: evaluation.componentSummary,
),
),
const SizedBox(width: ReclaimSpace.md),
TierBadge(tier: evaluation.tierLowest),
],
),
),
@ -163,44 +152,6 @@ class _NormRow extends StatelessWidget {
}
}
class _ComponentSummary extends StatelessWidget {
const _ComponentSummary({required this.summary});
final List<String> summary;
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
if (summary.isEmpty) {
return Text(
'',
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Komponenten',
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
),
const SizedBox(height: 4),
for (final line in summary)
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
line,
style: t.bodyLarge?.copyWith(
height: 1.35,
fontSize: 12.5,
),
),
),
],
);
}
}
class _MetricColumn extends StatelessWidget {
const _MetricColumn({required this.label, required this.value});

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import '../data/repository.dart';
import '../theme/reclaim_tokens.dart';
import '../widgets/brand_wordmark.dart';
import '../widgets/delta_mark.dart';
import 'about_page.dart';
import 'heatmap_page.dart';
@ -52,7 +51,14 @@ class _ShellPageState extends State<ShellPage> {
children: [
const DeltaMark(size: 36),
const SizedBox(height: ReclaimSpace.sm),
const BrandWordmark(size: 18),
Text(
'Recl∆Im',
style: t.labelLarge?.copyWith(
letterSpacing: 1.5,
color: ReclaimColors.signal,
fontWeight: FontWeight.w600,
),
),
Text(
'F∆I',
style: t.labelSmall?.copyWith(

View file

@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import '../theme/reclaim_tokens.dart';
/// "Recl∆Im" wordmark with the I glyphs petrol-coloured the
/// brand rule of thumb in the fai_web universe: wherever the name
/// stands as a brand, the I tinted petrol carries the FI-family
/// identity. As plain text it would all share the surrounding ink
/// colour.
///
/// Pass [size] for the base font size; the I scales relative.
class BrandWordmark extends StatelessWidget {
const BrandWordmark({
super.key,
this.size = 24,
this.fontWeight = FontWeight.w600,
this.color,
this.letterSpacing = -0.5,
});
final double size;
final FontWeight fontWeight;
/// Color of the non-I letters. Defaults to the surface's
/// onSurface so the wordmark reads against whichever ink/paper
/// backdrop it sits on.
final Color? color;
final double letterSpacing;
@override
Widget build(BuildContext context) {
final base = color ?? Theme.of(context).colorScheme.onSurface;
final style = TextStyle(
fontSize: size,
fontWeight: fontWeight,
fontFamily: ReclaimTypography.display,
color: base,
letterSpacing: letterSpacing,
height: 1.0,
);
return RichText(
text: TextSpan(
style: style,
children: [
const TextSpan(text: 'Recl'),
TextSpan(
text: '∆I',
style: style.copyWith(color: ReclaimColors.signal),
),
const TextSpan(text: 'm'),
],
),
);
}
}

View file

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reclaim_app/main.dart';
import 'package:reclaim_app/widgets/brand_wordmark.dart';
void main() {
// Generous surface so NavigationRail + Heatmap fit without
@ -21,7 +20,7 @@ void main() {
await tester.binding.setSurfaceSize(surfaceSize);
await tester.pumpWidget(const ReclaimApp());
await tester.pump();
expect(find.byType(BrandWordmark), findsWidgets);
expect(find.text('Recl∆Im'), findsOneWidget);
expect(
find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'),
findsOneWidget,