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>
127 lines
3.7 KiB
Dart
127 lines
3.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'reclaim_tokens.dart';
|
|
|
|
/// ThemeData assembly for the F∆I Recl∆Im client.
|
|
///
|
|
/// Dark mode is the default (consistent with fai_web's
|
|
/// `color-scheme: dark`). Light mode falls back to the paper
|
|
/// palette for printing/screenshot work.
|
|
class ReclaimTheme {
|
|
ReclaimTheme._();
|
|
|
|
static ThemeData dark() => _build(Brightness.dark);
|
|
static ThemeData light() => _build(Brightness.light);
|
|
|
|
static ThemeData _build(Brightness brightness) {
|
|
final isDark = brightness == Brightness.dark;
|
|
final canvas = isDark ? ReclaimColors.ink : ReclaimColors.paper;
|
|
final surface =
|
|
isDark ? ReclaimColors.inkRaised : ReclaimColors.paperRaised;
|
|
final onSurface = isDark
|
|
? const Color(0xFFE9E7E2)
|
|
: const Color(0xFF1A1B1E);
|
|
|
|
final colorScheme = ColorScheme(
|
|
brightness: brightness,
|
|
primary: ReclaimColors.signal,
|
|
onPrimary: ReclaimColors.ink,
|
|
secondary: ReclaimColors.mute,
|
|
onSecondary: onSurface,
|
|
error: const Color(0xFFD8723A),
|
|
onError: ReclaimColors.ink,
|
|
surface: surface,
|
|
onSurface: onSurface,
|
|
);
|
|
|
|
final textTheme = _textTheme(onSurface);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
brightness: brightness,
|
|
colorScheme: colorScheme,
|
|
scaffoldBackgroundColor: canvas,
|
|
canvasColor: canvas,
|
|
fontFamily: ReclaimTypography.body,
|
|
textTheme: textTheme,
|
|
cardTheme: CardThemeData(
|
|
color: surface,
|
|
elevation: 0,
|
|
margin: EdgeInsets.zero,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(ReclaimRadius.md),
|
|
side: BorderSide(color: Color(0x24FFFFFF)),
|
|
),
|
|
),
|
|
dividerTheme: DividerThemeData(
|
|
color: onSurface.withValues(alpha: ReclaimColors.hairlineDarkOpacity),
|
|
thickness: 1,
|
|
space: 1,
|
|
),
|
|
filledButtonTheme: FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: ReclaimColors.signal,
|
|
foregroundColor: ReclaimColors.ink,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(ReclaimRadius.sm),
|
|
),
|
|
textStyle: textTheme.labelLarge,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: ReclaimSpace.lg,
|
|
vertical: ReclaimSpace.md,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
static TextTheme _textTheme(Color onSurface) {
|
|
const display = ReclaimTypography.display;
|
|
const body = ReclaimTypography.body;
|
|
return TextTheme(
|
|
displaySmall: TextStyle(
|
|
fontFamily: display,
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: -0.02 * 28,
|
|
color: onSurface,
|
|
),
|
|
headlineSmall: TextStyle(
|
|
fontFamily: display,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: -0.02 * 20,
|
|
color: onSurface,
|
|
),
|
|
titleMedium: TextStyle(
|
|
fontFamily: body,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurface,
|
|
),
|
|
bodyLarge: TextStyle(
|
|
fontFamily: body,
|
|
fontSize: 14,
|
|
height: 1.45,
|
|
color: onSurface,
|
|
),
|
|
labelLarge: TextStyle(
|
|
fontFamily: body,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurface,
|
|
),
|
|
labelSmall: TextStyle(
|
|
fontFamily: body,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w500,
|
|
color: onSurface.withValues(alpha: 0.72),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Convenience: JetBrains-Mono variant of an existing style for
|
|
/// technical content (ELI/CELEX, SHA-256, audit-event IDs).
|
|
static TextStyle mono(TextStyle base) =>
|
|
base.copyWith(fontFamily: ReclaimTypography.mono);
|
|
}
|