From c6400302dd44dad3bb7e11eb65b534a6754158bd Mon Sep 17 00:00:00 2001 From: flemming-it Date: Thu, 18 Jun 2026 15:06:13 +0200 Subject: [PATCH] =?UTF-8?q?feat(brand):=20BrandWordmark=20renders=20?= =?UTF-8?q?=E2=88=86I=20in=20petrol,=20lifts=20the=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a dedicated BrandWordmark widget that splits 'Recl∆Im' into three TextSpans and tints the middle ∆I in the petrol signal colour. The fai_web universe uses this same accent rule for the F∆I and Ch∆In glyphs — Recl∆Im now follows it, so the brand stays visually coherent across every F∆I surface. Where it lands: - Landing page: large DeltaMark (96 px) and BrandWordmark (56 px) sit on a single row, the wordmark to the right of the mark. The two together carry the brand identity that a viewer sees first when opening the app. - NavigationRail leading: the muted 'Recl∆Im' Text line is replaced with the petrol-accented BrandWordmark (18 px), keeping the vendor tag 'F∆I' as a small muted line under it. widget_test.dart adapts: the literal 'Recl∆Im'-find no longer matches because the wordmark is now three text spans, so the test checks for BrandWordmark by type — equivalent assertion, robust to glyph-level splits. Signed-off-by: flemming-it --- app/lib/pages/landing_page.dart | 18 +++++++--- app/lib/pages/shell_page.dart | 10 ++---- app/lib/widgets/brand_wordmark.dart | 55 +++++++++++++++++++++++++++++ app/test/widget_test.dart | 3 +- 4 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 app/lib/widgets/brand_wordmark.dart diff --git a/app/lib/pages/landing_page.dart b/app/lib/pages/landing_page.dart index 0f19bac..50fba50 100644 --- a/app/lib/pages/landing_page.dart +++ b/app/lib/pages/landing_page.dart @@ -3,6 +3,7 @@ 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'; @@ -20,7 +21,7 @@ class LandingPage extends StatelessWidget { body: SafeArea( child: Center( child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 720), + constraints: const BoxConstraints(maxWidth: 820), child: Padding( padding: const EdgeInsets.symmetric( horizontal: ReclaimSpace.xxl, @@ -30,9 +31,18 @@ class LandingPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const DeltaMark(size: 96), - const SizedBox(height: ReclaimSpace.xxl), - Text('Recl∆Im', style: t.displaySmall), + 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 SizedBox(height: ReclaimSpace.sm), Text( 'Pflicht-basierte Wirkungsanalyse von Rechtsnormen — ' diff --git a/app/lib/pages/shell_page.dart b/app/lib/pages/shell_page.dart index 5cf3da4..2f7b51a 100644 --- a/app/lib/pages/shell_page.dart +++ b/app/lib/pages/shell_page.dart @@ -2,6 +2,7 @@ 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'; @@ -51,14 +52,7 @@ class _ShellPageState extends State { children: [ const DeltaMark(size: 36), const SizedBox(height: ReclaimSpace.sm), - Text( - 'Recl∆Im', - style: t.labelLarge?.copyWith( - letterSpacing: 1.5, - color: ReclaimColors.signal, - fontWeight: FontWeight.w600, - ), - ), + const BrandWordmark(size: 18), Text( 'F∆I', style: t.labelSmall?.copyWith( diff --git a/app/lib/widgets/brand_wordmark.dart b/app/lib/widgets/brand_wordmark.dart new file mode 100644 index 0000000..14d6783 --- /dev/null +++ b/app/lib/widgets/brand_wordmark.dart @@ -0,0 +1,55 @@ +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 F∆I-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'), + ], + ), + ); + } +} diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart index eb83a95..30b5b41 100644 --- a/app/test/widget_test.dart +++ b/app/test/widget_test.dart @@ -5,6 +5,7 @@ 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 @@ -20,7 +21,7 @@ void main() { await tester.binding.setSurfaceSize(surfaceSize); await tester.pumpWidget(const ReclaimApp()); await tester.pump(); - expect(find.text('Recl∆Im'), findsOneWidget); + expect(find.byType(BrandWordmark), findsWidgets); expect( find.widgetWithText(FilledButton, 'App öffnen (Demo-Modus)'), findsOneWidget,