diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index 8489187..5f53aff 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -6,41 +6,62 @@ import 'package:flutter/material.dart'; /// - Dark Mode (Cockpit OLED): deep matte blacks and vibrant saturated accents. /// - Light Mode (Daylight HUD): high-contrast daylight surfaces for outdoor sunlight readability. abstract final class AppColors { - // Brand & High-Contrast Accents (Unified MotorSignal Palette) - static const Color cyberCyan = Color(0xFF00B4D8); - static const Color technicalBlue = Color(0xFF0077A8); - static const Color velocityBlue = Color(0xFF0096C7); - static const Color raceRed = Color(0xFFFF3B30); - static const Color hazardRed = Color(0xFFFF3B30); - static const Color launchGreen = Color(0xFF34C759); + // MotorSignal Canonical Brand Identity (Single hue family) + static const Color brandBlue = Color(0xFF0066FF); + static const Color brandBlueStrong = Color(0xFF0047B3); + static const Color brandContainerLight = Color(0xFFD9E7FF); + static const Color brandContainerDark = Color(0xFF142542); + static const Color onBrand = Color(0xFFFFFFFF); + static const Color onBrandContainerLight = Color(0xFF0047B3); + static const Color onBrandContainerDark = Color(0xFFD9E7FF); + + // Backward-compatible token aliases mapped to canonical MotorSignal Blue + static const Color cyberCyan = brandBlue; + static const Color technicalBlue = brandBlueStrong; + static const Color velocityBlue = brandBlue; + + // Semantic State Colors (Precision, not aggression) + static const Color semanticSuccess = Color(0xFF2E9B62); + static const Color semanticSuccessLight = Color(0xFF247D4F); // Contrast-adjusted for light mode readability (>= 4.5:1) + static const Color semanticWarning = Color(0xFFB97800); + static const Color semanticWarningLight = Color(0xFF8A5800); // Contrast-adjusted for light mode readability (>= 4.5:1) + static const Color semanticDanger = Color(0xFFC94444); + static const Color semanticDangerDark = Color(0xFFE05353); // Contrast-adjusted for dark mode readability (>= 4.5:1) + + // Telemetry & Legacy semantic aliases + static const Color raceRed = semanticDanger; + static const Color hazardRed = semanticDanger; + static const Color launchGreen = semanticSuccess; static const Color telemetryGreen = Color(0xFF30D158); - static const Color successGreen = Color(0xFF28A745); - static const Color warningAmber = Color(0xFFFFB300); - static const Color darkAmber = Color(0xFFC48100); + static const Color successGreen = semanticSuccess; + static const Color warningAmber = semanticWarning; + static const Color darkAmber = semanticWarningLight; - // Dark Mode Palette (Graphite & Technical Cockpit) - static const Color darkBackground = Color(0xFF0D0F13); - static const Color darkSurface = Color(0xFF13171E); - static const Color darkSurfaceContainer = Color(0xFF1A1F28); - static const Color darkSurfaceContainerHigh = Color(0xFF222935); - static const Color darkSurfaceContainerHighest = Color(0xFF2B3342); - static const Color darkOutline = Color(0xFF2B3342); - static const Color darkOutlineVariant = Color(0xFF1F242F); - static const Color darkOnSurface = Color(0xFFF1F3F5); - static const Color darkOnSurfaceVariant = Color(0xFF9EABB8); - static const Color darkPrimary = Color(0xFF00B4D8); - static const Color darkSecondary = Color(0xFFFF3B30); + // Dark Neutrals (Graphite & Technical Cockpit) + static const Color darkBackground = Color(0xFF0B0D0F); + static const Color darkSurface = Color(0xFF14171A); // primary surface + static const Color darkSurfaceContainer = Color(0xFF1D2226); // elevated surface + static const Color darkSurfaceContainerHigh = Color(0xFF252B30); + static const Color darkSurfaceContainerHighest = Color(0xFF2E353B); + static const Color darkOutline = Color(0xFF30363B); // divider/border + static const Color darkOutlineVariant = Color(0xFF22272B); + static const Color darkOnSurface = Color(0xFFF5F7F8); // primary text + static const Color darkOnSurfaceVariant = Color(0xFFAEB7BD); // secondary text + static const Color darkDisabled = Color(0xFF687278); // disabled text + static const Color darkPrimary = brandBlue; + static const Color darkSecondary = semanticDanger; - // Light Mode Palette (Daylight Technical HUD) - static const Color lightBackground = Color(0xFFF3F5F8); - static const Color lightSurface = Color(0xFFE9EDF2); + // Light Neutrals (Daylight Technical HUD) + static const Color lightBackground = Color(0xFFF2F3F3); + static const Color lightSurface = Color(0xFFFFFFFF); // primary surface static const Color lightSurfaceContainer = Color(0xFFFFFFFF); - static const Color lightSurfaceContainerHigh = Color(0xFFE1E6EC); - static const Color lightSurfaceContainerHighest = Color(0xFFD6DCE4); - static const Color lightOutline = Color(0xFFCBD2DC); - static const Color lightOutlineVariant = Color(0xFFDFE4EC); - static const Color lightOnSurface = Color(0xFF13171E); - static const Color lightOnSurfaceVariant = Color(0xFF5A6675); - static const Color lightPrimary = Color(0xFF0077A8); - static const Color lightSecondary = Color(0xFFD32F2F); + static const Color lightSurfaceContainerHigh = Color(0xFFFAFAFA); // elevated surface + static const Color lightSurfaceContainerHighest = Color(0xFFF0F2F4); + static const Color lightOutline = Color(0xFFD3D7D9); // divider/border + static const Color lightOutlineVariant = Color(0xFFE1E5E7); + static const Color lightOnSurface = Color(0xFF111417); // primary text + static const Color lightOnSurfaceVariant = Color(0xFF596269); // secondary text + static const Color lightDisabled = Color(0xFF9AA2A7); // disabled text + static const Color lightPrimary = brandBlue; + static const Color lightSecondary = semanticDanger; } diff --git a/lib/core/theme/app_typography.dart b/lib/core/theme/app_typography.dart index 7aca780..0578dcb 100644 --- a/lib/core/theme/app_typography.dart +++ b/lib/core/theme/app_typography.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; /// /// Multi-font architecture: /// - JetBrains Mono: Dynamic numbers, telemetry readouts, timers (zero numerical jitter). -/// - Sora: Headlines, app headers, compass directions. -/// - Inter: Body, labels, metadata, specifications. +/// - Montserrat: Brand-forward marketing, headlines, and headers. +/// - DM Sans: Application UI, body text, labels, and specifications. abstract final class AppTypography { static const String fontTelemetry = 'JetBrains Mono'; - static const String fontHeadline = 'Sora'; - static const String fontBody = 'Inter'; + static const String fontHeadline = 'Montserrat'; + static const String fontBody = 'DM Sans'; // Large Speedometer Readouts static const TextStyle speedometer = TextStyle( diff --git a/lib/core/theme/theme.dart b/lib/core/theme/theme.dart index c506112..1f2ca7f 100644 --- a/lib/core/theme/theme.dart +++ b/lib/core/theme/theme.dart @@ -10,19 +10,19 @@ class AppTheme { static ThemeData get darkTheme { const colorScheme = ColorScheme.dark( primary: AppColors.darkPrimary, - onPrimary: Color(0xFF000000), - primaryContainer: Color(0xFF004D56), - onPrimaryContainer: AppColors.cyberCyan, + onPrimary: AppColors.onBrand, + primaryContainer: AppColors.brandContainerDark, + onPrimaryContainer: AppColors.onBrandContainerDark, secondary: AppColors.darkSecondary, onSecondary: Color(0xFFFFFFFF), - secondaryContainer: Color(0xFF5A0000), - onSecondaryContainer: Color(0xFFFFB4AB), + secondaryContainer: Color(0xFF311414), + onSecondaryContainer: Color(0xFFFFDAD6), surface: AppColors.darkSurface, onSurface: AppColors.darkOnSurface, onSurfaceVariant: AppColors.darkOnSurfaceVariant, outline: AppColors.darkOutline, outlineVariant: AppColors.darkOutlineVariant, - error: AppColors.hazardRed, + error: AppColors.semanticDanger, onError: Color(0xFFFFFFFF), ); @@ -207,19 +207,19 @@ class AppTheme { static ThemeData get lightTheme { const colorScheme = ColorScheme.light( primary: AppColors.lightPrimary, - onPrimary: Color(0xFFFFFFFF), - primaryContainer: Color(0xFFD6E4FF), - onPrimaryContainer: Color(0xFF001B3E), + onPrimary: AppColors.onBrand, + primaryContainer: AppColors.brandContainerLight, + onPrimaryContainer: AppColors.onBrandContainerLight, secondary: AppColors.lightSecondary, onSecondary: Color(0xFFFFFFFF), - secondaryContainer: Color(0xFFFFDAD6), + secondaryContainer: Color(0xFFFDE8E8), onSecondaryContainer: Color(0xFF410002), surface: AppColors.lightSurface, onSurface: AppColors.lightOnSurface, onSurfaceVariant: AppColors.lightOnSurfaceVariant, outline: AppColors.lightOutline, outlineVariant: AppColors.lightOutlineVariant, - error: AppColors.hazardRed, + error: AppColors.semanticDanger, onError: Color(0xFFFFFFFF), ); diff --git a/lib/core/theme/theme_extensions.dart b/lib/core/theme/theme_extensions.dart index 12afdf5..2ddaf63 100644 --- a/lib/core/theme/theme_extensions.dart +++ b/lib/core/theme/theme_extensions.dart @@ -47,39 +47,39 @@ class MotorSignalThemeColors extends ThemeExtension { /// Dark Mode default telemetry tokens (Cockpit Precision). static const MotorSignalThemeColors dark = MotorSignalThemeColors( cyberCyan: AppColors.darkPrimary, - velocityBlue: AppColors.velocityBlue, - raceRed: AppColors.raceRed, - launchGreen: AppColors.launchGreen, - warningAmber: AppColors.warningAmber, + velocityBlue: AppColors.brandBlueStrong, + raceRed: AppColors.semanticDangerDark, + launchGreen: AppColors.semanticSuccess, + warningAmber: AppColors.semanticWarning, telemetryGreen: AppColors.telemetryGreen, leanAngleLeft: AppColors.darkPrimary, - leanAngleRight: AppColors.velocityBlue, - gForcePositive: AppColors.launchGreen, - gForceNegative: AppColors.raceRed, + leanAngleRight: AppColors.brandBlueStrong, + gForcePositive: AppColors.semanticSuccess, + gForceNegative: AppColors.semanticDangerDark, speedometerDial: AppColors.darkPrimary, - speedometerGlow: Color(0x1400B4D8), + speedometerGlow: Color(0x140066FF), surfaceContainer: AppColors.darkSurfaceContainer, surfaceContainerHigh: AppColors.darkSurfaceContainerHigh, surfaceContainerHighest: AppColors.darkSurfaceContainerHighest, outline: AppColors.darkOutline, outlineVariant: AppColors.darkOutlineVariant, - glowShadow: Color(0x1F00B4D8), + glowShadow: Color(0x1F0066FF), ); /// Light Mode default telemetry tokens (Daylight HUD). static const MotorSignalThemeColors light = MotorSignalThemeColors( cyberCyan: AppColors.lightPrimary, - velocityBlue: AppColors.velocityBlue, - raceRed: AppColors.lightSecondary, - launchGreen: AppColors.launchGreen, - warningAmber: AppColors.warningAmber, + velocityBlue: AppColors.brandBlueStrong, + raceRed: AppColors.semanticDanger, + launchGreen: AppColors.semanticSuccessLight, + warningAmber: AppColors.semanticWarningLight, telemetryGreen: AppColors.telemetryGreen, leanAngleLeft: AppColors.lightPrimary, - leanAngleRight: AppColors.velocityBlue, - gForcePositive: AppColors.launchGreen, - gForceNegative: AppColors.lightSecondary, + leanAngleRight: AppColors.brandBlueStrong, + gForcePositive: AppColors.semanticSuccessLight, + gForceNegative: AppColors.semanticDanger, speedometerDial: AppColors.lightPrimary, - speedometerGlow: Color(0x0F0077A8), + speedometerGlow: Color(0x0F0066FF), surfaceContainer: AppColors.lightSurfaceContainer, surfaceContainerHigh: AppColors.lightSurfaceContainerHigh, surfaceContainerHighest: AppColors.lightSurfaceContainerHighest, @@ -88,6 +88,16 @@ class MotorSignalThemeColors extends ThemeExtension { glowShadow: Color(0x14000000), ); + Color get brandBlue => cyberCyan; + Color get brandBlueStrong => velocityBlue; + Color get brandContainer => surfaceContainer == AppColors.darkSurfaceContainer + ? AppColors.brandContainerDark + : AppColors.brandContainerLight; + Color get onBrand => AppColors.onBrand; + Color get onBrandContainer => surfaceContainer == AppColors.darkSurfaceContainer + ? AppColors.onBrandContainerDark + : AppColors.onBrandContainerLight; + @override MotorSignalThemeColors copyWith({ Color? cyberCyan, diff --git a/lib/features/settings/settings_screen.dart b/lib/features/settings/settings_screen.dart index 8df29ac..5af3cc2 100644 --- a/lib/features/settings/settings_screen.dart +++ b/lib/features/settings/settings_screen.dart @@ -6,6 +6,7 @@ import '../../core/app_info.dart'; import '../../core/responsive/viewport_size.dart'; import '../../core/theme/theme_extensions.dart'; import '../../core/theme/theme_mode_provider.dart'; +import '../../core/theme/app_typography.dart'; import '../../database/database.dart'; import '../../features/rides/ride_state_machine.dart'; import '../../services/alerts/road_alert_models.dart'; @@ -206,7 +207,7 @@ class _SettingsScreenState extends ConsumerState { return Text( title, style: TextStyle( - fontFamily: 'Sora', + fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 0.2, diff --git a/lib/features/settings/widgets/current_plan_card.dart b/lib/features/settings/widgets/current_plan_card.dart index dfb7415..93edc9f 100644 --- a/lib/features/settings/widgets/current_plan_card.dart +++ b/lib/features/settings/widgets/current_plan_card.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../core/theme/theme_extensions.dart'; +import '../../../core/theme/app_typography.dart'; import '../../../services/billing/product_catalog.dart'; import '../../../services/entitlements/entitlement_providers.dart'; import '../../../services/entitlements/entitlement_state.dart'; @@ -123,7 +124,7 @@ class CurrentPlanCard extends ConsumerWidget { Text( planTitle, style: TextStyle( - fontFamily: 'Sora', + fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, color: isPro ? colors.cyberCyan : Theme.of(context).colorScheme.onSurface, diff --git a/lib/features/social/widgets/avatar_badge.dart b/lib/features/social/widgets/avatar_badge.dart index eefeaa2..c6699ee 100644 --- a/lib/features/social/widgets/avatar_badge.dart +++ b/lib/features/social/widgets/avatar_badge.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../../core/theme/app_typography.dart'; import '../../../services/social/rider_profile_models.dart'; /// Renders a rider's avatar using their preset styling or custom photo URL. @@ -56,7 +57,7 @@ class AvatarBadge extends StatelessWidget { color: primaryColor, fontWeight: FontWeight.w900, fontSize: radius * 0.9, - fontFamily: 'Sora', + fontFamily: AppTypography.fontHeadline, ), ), ), diff --git a/test/theme_test.dart b/test/theme_test.dart index e7ac07d..71e7f0d 100644 --- a/test/theme_test.dart +++ b/test/theme_test.dart @@ -13,27 +13,42 @@ void main() { group('MotorSignal Design System - Tokens & Hierarchy', () { test('AppColors verifies dark and light semantic palette definitions', () { - expect(AppColors.cyberCyan, const Color(0xFF00B4D8)); - expect(AppColors.velocityBlue, const Color(0xFF0096C7)); - expect(AppColors.raceRed, const Color(0xFFFF3B30)); - expect(AppColors.darkBackground, const Color(0xFF0D0F13)); - expect(AppColors.darkSurface, const Color(0xFF13171E)); - expect(AppColors.lightBackground, const Color(0xFFF3F5F8)); - expect(AppColors.lightSurface, const Color(0xFFE9EDF2)); + expect(AppColors.brandBlue, const Color(0xFF0066FF)); + expect(AppColors.brandBlueStrong, const Color(0xFF0047B3)); + expect(AppColors.brandContainerLight, const Color(0xFFD9E7FF)); + expect(AppColors.brandContainerDark, const Color(0xFF142542)); + expect(AppColors.cyberCyan, const Color(0xFF0066FF)); + expect(AppColors.semanticDanger, const Color(0xFFC94444)); + expect(AppColors.semanticSuccess, const Color(0xFF2E9B62)); + expect(AppColors.semanticWarning, const Color(0xFFB97800)); + expect(AppColors.darkBackground, const Color(0xFF0B0D0F)); + expect(AppColors.darkSurface, const Color(0xFF14171A)); + expect(AppColors.darkSurfaceContainer, const Color(0xFF1D2226)); + expect(AppColors.darkOutline, const Color(0xFF30363B)); + expect(AppColors.darkOnSurface, const Color(0xFFF5F7F8)); + expect(AppColors.darkOnSurfaceVariant, const Color(0xFFAEB7BD)); + expect(AppColors.darkDisabled, const Color(0xFF687278)); + expect(AppColors.lightBackground, const Color(0xFFF2F3F3)); + expect(AppColors.lightSurface, const Color(0xFFFFFFFF)); + expect(AppColors.lightSurfaceContainerHigh, const Color(0xFFFAFAFA)); + expect(AppColors.lightOutline, const Color(0xFFD3D7D9)); + expect(AppColors.lightOnSurface, const Color(0xFF111417)); + expect(AppColors.lightOnSurfaceVariant, const Color(0xFF596269)); + expect(AppColors.lightDisabled, const Color(0xFF9AA2A7)); }); test('AppTypography specifies authoritative multi-font system', () { expect(AppTypography.fontTelemetry, 'JetBrains Mono'); - expect(AppTypography.fontHeadline, 'Sora'); - expect(AppTypography.fontBody, 'Inter'); + expect(AppTypography.fontHeadline, 'Montserrat'); + expect(AppTypography.fontBody, 'DM Sans'); expect(AppTypography.speedometer.fontFamily, 'JetBrains Mono'); expect(AppTypography.speedometer.fontSize, 96); expect(AppTypography.speedometer.fontWeight, FontWeight.w900); - expect(AppTypography.headingCompass.fontFamily, 'Sora'); - expect(AppTypography.bodyMd.fontFamily, 'Inter'); - expect(AppTypography.button.fontFamily, 'Sora'); + expect(AppTypography.headingCompass.fontFamily, 'Montserrat'); + expect(AppTypography.bodyMd.fontFamily, 'DM Sans'); + expect(AppTypography.button.fontFamily, 'Montserrat'); }); test('AppSpacing and AppRadius conform to 4dp base grid and 48dp touch targets', () { @@ -55,13 +70,13 @@ void main() { group('MotorSignalThemeColors Extension', () { test('Dark and Light presets have correct brightness semantics', () { const dark = MotorSignalThemeColors.dark; - expect(dark.cyberCyan, AppColors.cyberCyan); - expect(dark.launchGreen, AppColors.launchGreen); + expect(dark.cyberCyan, AppColors.brandBlue); + expect(dark.launchGreen, AppColors.semanticSuccess); expect(dark.surfaceContainer, AppColors.darkSurfaceContainer); const light = MotorSignalThemeColors.light; - expect(light.cyberCyan, AppColors.technicalBlue); - expect(light.launchGreen, AppColors.launchGreen); + expect(light.cyberCyan, AppColors.brandBlue); + expect(light.launchGreen, AppColors.semanticSuccessLight); expect(light.surfaceContainer, AppColors.lightSurfaceContainer); }); @@ -99,7 +114,7 @@ void main() { final ext = theme.extension(); expect(ext, isNotNull); - expect(ext?.cyberCyan, AppColors.technicalBlue); + expect(ext?.cyberCyan, AppColors.brandBlue); }); });