diff --git a/apps/app_seeds/lib/ui/inventory_list_screen.dart b/apps/app_seeds/lib/ui/inventory_list_screen.dart index f782e23..a3442ed 100644 --- a/apps/app_seeds/lib/ui/inventory_list_screen.dart +++ b/apps/app_seeds/lib/ui/inventory_list_screen.dart @@ -233,8 +233,7 @@ class _VarietyTile extends StatelessWidget { ), trailing: IconButton( icon: const Icon(Icons.edit_outlined), - // Action colour (≥3:1 on the canvas) so the control stays legible; - // seedMuted here fell to 2.62:1. + // Action colour: this is a tap target, not secondary text. color: seedGreen, tooltip: context.t.common.edit, onPressed: open, diff --git a/apps/app_seeds/lib/ui/theme.dart b/apps/app_seeds/lib/ui/theme.dart index 1af7383..392e37a 100644 --- a/apps/app_seeds/lib/ui/theme.dart +++ b/apps/app_seeds/lib/ui/theme.dart @@ -6,7 +6,9 @@ import 'package:flutter/material.dart'; /// headers) and a soft green container for selected/tonal surfaces. const seedGreen = Color(0xFF2F7D34); // primary (actions, links, headers) const seedGreenDark = Color(0xFF22521E); // status bar / darker green -const seedAppBar = Color(0xFF3E8C38); // app-bar green +// App-bar green. Nudged down from the mockups' #3E8C38 so its white title +// meets WCAG AA (4.5:1) — see test/ui/theme_contrast_test.dart. +const seedAppBar = Color(0xFF3A8434); const seedCanvas = Color(0xFFE7F1E0); // surface / scaffold const seedPrimaryContainer = Color(0xFFD3E8C8); // tonal green container const seedOnPrimaryContainer = Color(0xFF2F6D2A); @@ -18,7 +20,10 @@ const seedDivider = Color(0xFFE4E9DD); const seedTitle = Color(0xFF1B2416); // heading text on canvas const seedOnSurface = Color(0xFF1B2416); const seedOnSurfaceVariant = Color(0xFF5C6B52); // scientific names, secondary -const seedMuted = Color(0xFF8A9880); // hints, list edit icon +// Hints and secondary text/icons. Darkened from the mockups' #8A9880 (which +// fell below WCAG AA) to keep ≥4.5:1 on white, the canvas and the field fill — +// see test/ui/theme_contrast_test.dart. +const seedMuted = Color(0xFF617057); const seedRating = Color(0xFFE8A200); // stars ThemeData buildTaneTheme() { diff --git a/apps/app_seeds/test/ui/theme_contrast_test.dart b/apps/app_seeds/test/ui/theme_contrast_test.dart new file mode 100644 index 0000000..1b514ac --- /dev/null +++ b/apps/app_seeds/test/ui/theme_contrast_test.dart @@ -0,0 +1,52 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart' show Colors; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tane/ui/theme.dart'; + +/// WCAG 2.x relative luminance of a color (sRGB). +double _luminance(Color c) { + double channel(double v) => + v <= 0.03928 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4).toDouble(); + return 0.2126 * channel(c.r) + 0.7152 * channel(c.g) + 0.0722 * channel(c.b); +} + +/// WCAG contrast ratio between two colors (order-independent, 1..21). +double _contrast(Color a, Color b) { + final la = _luminance(a); + final lb = _luminance(b); + final lighter = max(la, lb); + final darker = min(la, lb); + return (lighter + 0.05) / (darker + 0.05); +} + +void main() { + void expectAa(Color foreground, Color background, String label) { + expect( + _contrast(foreground, background), + greaterThanOrEqualTo(4.5), + reason: '$label must meet WCAG AA (4.5:1) for normal text', + ); + } + + group('palette meets WCAG AA (4.5:1) for text', () { + test('seedMuted (hints, subtitles) on every background it sits on', () { + // White: search-field fill and home cards. Canvas/field: M3 surfaces. + expectAa(seedMuted, Colors.white, 'seedMuted on white'); + expectAa(seedMuted, seedCanvas, 'seedMuted on canvas'); + expectAa(seedMuted, seedField, 'seedMuted on field fill'); + }); + + test('body and secondary text on the canvas', () { + expectAa(seedOnSurface, seedCanvas, 'onSurface on canvas'); + expectAa(seedTitle, seedCanvas, 'titles on canvas'); + expectAa(seedOnSurfaceVariant, seedCanvas, 'onSurfaceVariant on canvas'); + }); + + test('white text on the green action colours', () { + expectAa(Colors.white, seedGreen, 'white on primary green'); + expectAa(Colors.white, seedAppBar, 'white on app-bar green'); + }); + }); +}