/// Ported from `com.rippr.ui.theme.RipprTheme`. /// /// Dark-only by design for a pocketed ride -- read at a glance, in a pocket-glance, at /// night as much as in daylight. V3-05 adds [ripprMountedTheme], a deliberate exception /// for the one screen that is actually looked at continuously, in direct daylight, behind /// a visor -- where the dark ground loses to glare instead of helping. library; import 'package:flutter/material.dart'; // Matches the launcher icon: near-black tarmac with a safety-orange accent. const _accent = Color(0xFFFF5722); const _ground = Color(0xFF101418); const _surface = Color(0xFF181D23); const _ink = Color(0xFFF2F5F8); const _outline = Color(0xFF66727E); const _danger = Color(0xFFE53935); const ripprColors = ColorScheme.dark( primary: _accent, onPrimary: Color(0xFF100A06), secondary: _outline, surface: _surface, onSurface: _ink, surfaceContainerHighest: Color(0xFF212832), onSurfaceVariant: _ink, outline: _outline, error: _danger, onError: _ink, ); /// The near-black ground the whole app sits on. const ripprBackground = _ground; ThemeData ripprTheme() { final base = ThemeData( colorScheme: ripprColors, useMaterial3: true, scaffoldBackgroundColor: _ground, ); return base.copyWith( // Compose's `Surface` was load-bearing, not decoration: it set `LocalContentColor`. // Without it, any Text that did not name a colour fell back to black — invisible on // this near-black ground, and **no test caught it; only a screenshot did**. // // Flutter's equivalent exposure is the default text colour. Setting it explicitly // here, rather than relying on a wrapping widget, means the failure cannot recur by // someone removing a container. textTheme: base.textTheme.apply(bodyColor: _ink, displayColor: _ink), appBarTheme: const AppBarTheme( backgroundColor: _ground, foregroundColor: _ink, elevation: 0, ), dividerTheme: const DividerThemeData(color: Color(0xFF2A323C), space: 1), ); } // V3-05: mounted colours. Near-white ground, near-black ink -- the inverse tradeoff from // the pocketed theme above. Read against a visor in direct daylight, a dark ground loses // to glare in a way a dark theme never has to answer for at a pocket glance. const _mountedGround = Color(0xFFF5F3EF); const _mountedInk = Color(0xFF12100D); const _mountedAccent = Color(0xFFD84315); const ripprMountedColors = ColorScheme.light( primary: _mountedAccent, onPrimary: Color(0xFFFFFFFF), secondary: Color(0xFF4A4034), surface: Color(0xFFFFFFFF), onSurface: _mountedInk, surfaceContainerHighest: Color(0xFFE7E2D9), onSurfaceVariant: _mountedInk, outline: Color(0xFF706A5E), error: Color(0xFFB3261E), onError: Color(0xFFFFFFFF), ); const ripprMountedBackground = _mountedGround; /// Scales body/headline text up for legibility at a glance, at speed, behind a visor. /// Kept separate from V3-16's visual identity work -- this is a stopgap born of a real /// constraint, not a design pass. const double mountedTextScale = 1.25; /// V3-05's mounted mode: high contrast, larger text. Only applied to the record screen /// while mounted, not the whole app -- everywhere else is still read at a pocket glance. ThemeData ripprMountedTheme() { final base = ThemeData( colorScheme: ripprMountedColors, useMaterial3: true, scaffoldBackgroundColor: _mountedGround, ); return base.copyWith( textTheme: base.textTheme.apply( bodyColor: _mountedInk, displayColor: _mountedInk, ), appBarTheme: const AppBarTheme( backgroundColor: _mountedGround, foregroundColor: _mountedInk, elevation: 0, ), dividerTheme: const DividerThemeData(color: Color(0xFFDAD4C8), space: 1), ); } /// Monospace for anything numeric. /// /// Digits that change every second must not make the layout jitter, which proportional /// figures do. Used for every speed, distance and clock in the app. const TextStyle monoDigits = TextStyle( fontFamily: 'monospace', fontFeatures: [FontFeature.tabularFigures()], );