diff --git a/.claude/context/styling.md b/.claude/context/styling.md index 6e0c4ea..1f78a3e 100644 --- a/.claude/context/styling.md +++ b/.claude/context/styling.md @@ -29,6 +29,13 @@ widgets. Key points: whole app in `main.dart`. When available and `useDynamicColor` is on, the OS-provided `ColorScheme` wins over the seed color — always thread both `dynamicScheme` and `useDynamicColor` through when adding a theme knob. + `NooColors.fromDynamicScheme` keeps `bg` on the OS `surface` but picks + `surface` (cards, selection bar, rows) from the container roles so it + stays lighter than `bg`: `surfaceContainerLowest` (white) in light, + `surfaceContainer` in dark. The dynamic_color package seeds the container + roles from the primary color rather than the OS palette, and in light mode + `surfaceContainerLow` is slightly *darker* than the OS `surface`, which + made cards blend into the page ("the selection bar has no background"). - **Font**: Inter via `google_fonts`, applied through `GoogleFonts.interTextTheme(...)`. Font files ship in `assets/google_fonts/` (Instrument Sans, Schibsted Grotesk) and diff --git a/lib/theme/design_tokens.dart b/lib/theme/design_tokens.dart index 4f2fbbe..672fbe4 100644 --- a/lib/theme/design_tokens.dart +++ b/lib/theme/design_tokens.dart @@ -139,11 +139,20 @@ class NooColors extends ThemeExtension { /// paints it, not painted directly - using it as-is for a sheet/dialog's /// `barrierColor` hid the screen behind completely instead of dimming it, /// so this uses the fixed palette's already-translucent scrim instead. + /// + /// `surface` (cards, bars, rows) has to read as lighter than `bg`, like + /// the fixed palettes' white-on-beige / lifted-on-near-black. In light + /// mode `surfaceContainerLow` is a hair *darker* than the OS-provided + /// `surface` that [bg] uses, so cards (the selection bar, list rows) + /// vanished into the page; `surfaceContainerLowest` (white) doesn't. Dark + /// mode uses `surfaceContainer` rather than `...Low` for a bigger step + /// off the page. factory NooColors.fromDynamicScheme(ColorScheme cs) { - final fixed = cs.brightness == Brightness.dark ? dark : light; + final dark = cs.brightness == Brightness.dark; + final fixed = dark ? NooColors.dark : NooColors.light; return NooColors( bg: cs.surface, - surface: cs.surfaceContainerLow, + surface: dark ? cs.surfaceContainer : cs.surfaceContainerLowest, surface2: cs.surfaceContainerHigh, surface3: cs.surfaceContainerHighest, line: cs.outlineVariant, diff --git a/test/widgets/noo/core_test.dart b/test/widgets/noo/core_test.dart index 0bd4af8..143d373 100644 --- a/test/widgets/noo/core_test.dart +++ b/test/widgets/noo/core_test.dart @@ -74,6 +74,30 @@ void main() { }); }); + group('NooColors.fromDynamicScheme surfaces', () { + for (final brightness in Brightness.values) { + test('cards are clearly lighter than the page (${brightness.name})', () { + // The OS palette overrides `surface` (what `bg` maps to) but not the + // seeded container roles - mimic that mismatch. + final seeded = ColorScheme.fromSeed( + seedColor: const Color(0xFF3F6B8F), + brightness: brightness, + ); + final os = seeded.copyWith( + surface: brightness == Brightness.light + ? const Color(0xFFF7F9FC) + : const Color(0xFF101417), + ); + final c = NooColors.fromDynamicScheme(os); + // WCAG-style contrast ratio between the card and the page. + final hi = c.surface.computeLuminance(); + final lo = c.bg.computeLuminance(); + expect(hi, greaterThan(lo)); + expect((hi + 0.05) / (lo + 0.05), greaterThan(1.04)); + }); + } + }); + group('NooAvatar', () { testNooWidgets('current user uses accent-soft/accent-text', ( tester,