import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; /// DriverVault design tokens + Material theme, mirroring the web app's design /// system (blue-led cool palette, Archivo + DM Mono type, 22px cards, soft /// cool shadows). Every screen styles through this so light/dark theme for free. class DriverVault { DriverVault._(); // ---- Brand blue ramp (from the DriverVault mark) ---- static const brand900 = Color(0xFF0B1730); static const brand800 = Color(0xFF0F1E3D); static const brand700 = Color(0xFF1E40AF); static const brand600 = Color(0xFF2563EB); static const brand500 = Color(0xFF3B82F6); static const brand400 = Color(0xFF60A5FA); static const brand300 = Color(0xFF93C5FD); static const brand100 = Color(0xFFE8F0FD); // ---- Cool neutrals ---- static const ink900 = Color(0xFF0F1E3D); static const ink600 = Color(0xFF3E4E68); static const ink500 = Color(0xFF5C6B85); static const ink400 = Color(0xFF7A8AA6); static const ink200 = Color(0xFFD6DEEA); static const ink100 = Color(0xFFE4E9F2); static const ink50 = Color(0xFFEEF2F8); static const ink25 = Color(0xFFF7F9FC); // ---- Semantic status (car: OK / due / fault) ---- static const success = Color(0xFF1F8A5B); static const warning = Color(0xFFD9822B); static const danger = Color(0xFFDC2A45); static const info = Color(0xFF2563EB); // Light status tints static const successSoft = Color(0xFFE1F3EA); static const warningSoft = Color(0xFFFBEDDD); static const dangerSoft = Color(0xFFFBE3E7); static const infoSoft = Color(0xFFE8F0FD); // Dark status tints (re-cut for dark surfaces) static const successSoftDark = Color(0xFF12352A); static const warningSoftDark = Color(0xFF3A2A16); static const dangerSoftDark = Color(0xFF3A1620); static const infoSoftDark = Color(0xFF122A4D); // ---- Dark surfaces / text / borders ---- static const darkPage = Color(0xFF0B1730); static const darkCard = Color(0xFF13233F); static const darkSunken = Color(0xFF0F1E38); static const darkBorder = Color(0xFF21324F); static const darkBorderStrong = Color(0xFF2C3F5E); static const darkTextStrong = Color(0xFFF2F6FC); static const darkTextBody = Color(0xFFB7C4D9); static const darkTextMuted = Color(0xFF7C8CA8); static const darkAccent = Color(0xFF3B82F6); static const radiusControl = 12.0; static const radiusCard = 22.0; static bool isDark(BuildContext c) => Theme.of(c).brightness == Brightness.dark; // Brand tint used for avatars / info chips (adapts to theme). static Color brandTint(BuildContext c) => isDark(c) ? const Color(0xFF17294A) : brand100; static Color brandOnTint(BuildContext c) => isDark(c) ? brand300 : brand700; /// Muted secondary text colour (replaces ad-hoc Colors.grey). static Color muted(BuildContext c) => isDark(c) ? darkTextMuted : ink400; /// DM Mono style for data / units / labels. static TextStyle mono(BuildContext c, {double size = 13, FontWeight weight = FontWeight.w500, Color? color}) => GoogleFonts.dmMono( fontSize: size, fontWeight: weight, letterSpacing: 0.2, color: color ?? Theme.of(c).textTheme.bodyMedium?.color, ); static ThemeData theme(Brightness brightness) { final dark = brightness == Brightness.dark; final scheme = ColorScheme( brightness: brightness, primary: dark ? darkAccent : brand600, onPrimary: Colors.white, primaryContainer: dark ? const Color(0xFF17294A) : brand100, onPrimaryContainer: dark ? brand300 : brand700, secondary: dark ? brand400 : brand700, onSecondary: Colors.white, surface: dark ? darkCard : Colors.white, onSurface: dark ? darkTextStrong : ink900, surfaceContainerHighest: dark ? darkSunken : ink50, onSurfaceVariant: dark ? darkTextBody : ink600, outline: dark ? darkBorderStrong : ink200, outlineVariant: dark ? darkBorder : ink100, error: danger, onError: Colors.white, errorContainer: dark ? dangerSoftDark : dangerSoft, onErrorContainer: danger, ); final baseText = dark ? ThemeData.dark().textTheme : ThemeData.light().textTheme; return ThemeData( useMaterial3: true, brightness: brightness, colorScheme: scheme, scaffoldBackgroundColor: dark ? darkPage : ink25, textTheme: GoogleFonts.archivoTextTheme(baseText).apply( bodyColor: dark ? darkTextBody : ink600, displayColor: dark ? darkTextStrong : ink900, ), appBarTheme: AppBarTheme( backgroundColor: dark ? darkCard : Colors.white, foregroundColor: dark ? darkTextStrong : ink900, elevation: 0, scrolledUnderElevation: 0.5, centerTitle: false, titleTextStyle: GoogleFonts.archivo( fontSize: 20, fontWeight: FontWeight.w700, letterSpacing: -0.4, color: dark ? darkTextStrong : ink900, ), ), cardTheme: CardThemeData( color: dark ? darkCard : Colors.white, elevation: 0, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusCard), side: BorderSide(color: dark ? darkBorder : ink100), ), ), dividerColor: dark ? darkBorder : ink100, dividerTheme: DividerThemeData(color: dark ? darkBorder : ink100, thickness: 1), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: dark ? darkSunken : Colors.white, isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusControl), borderSide: BorderSide(color: dark ? darkBorderStrong : ink200), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusControl), borderSide: BorderSide(color: dark ? darkBorderStrong : ink200), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusControl), borderSide: BorderSide(color: dark ? darkAccent : brand600, width: 2), ), labelStyle: TextStyle(color: dark ? darkTextMuted : ink500), floatingLabelStyle: TextStyle(color: dark ? darkAccent : brand600), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: dark ? darkAccent : brand600, foregroundColor: Colors.white, textStyle: GoogleFonts.archivo(fontWeight: FontWeight.w600, fontSize: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusControl)), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: dark ? darkTextBody : ink600, side: BorderSide(color: dark ? darkBorderStrong : ink200), textStyle: GoogleFonts.archivo(fontWeight: FontWeight.w600, fontSize: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusControl)), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: dark ? brand300 : brand700, textStyle: GoogleFonts.archivo(fontWeight: FontWeight.w600, fontSize: 14), ), ), floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: dark ? darkAccent : brand600, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusControl)), ), chipTheme: ChipThemeData( backgroundColor: dark ? darkSunken : ink50, side: BorderSide(color: dark ? darkBorder : ink100), ), navigationBarTheme: NavigationBarThemeData( backgroundColor: dark ? darkCard : Colors.white, surfaceTintColor: Colors.transparent, indicatorColor: dark ? const Color(0xFF17294A) : brand100, elevation: 0, height: 66, labelTextStyle: WidgetStateProperty.resolveWith((states) { final on = states.contains(WidgetState.selected); return GoogleFonts.archivo( fontSize: 11, fontWeight: on ? FontWeight.w600 : FontWeight.w500, color: on ? (dark ? brand300 : brand700) : (dark ? darkTextMuted : ink400), ); }), iconTheme: WidgetStateProperty.resolveWith((states) { final on = states.contains(WidgetState.selected); return IconThemeData( size: 24, color: on ? (dark ? brand300 : brand700) : (dark ? darkTextMuted : ink400), ); }), ), snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating), ); } } /// The DriverVault "Fast Forward" mark — three forward-leaning rounded bars of /// increasing height (skewX(-13°)), reading as acceleration / momentum. class DriverVaultMark extends StatelessWidget { final double size; const DriverVaultMark({super.key, this.size = 32}); @override Widget build(BuildContext context) { final u = size / 40; // scale factor Widget bar(double h, Color c) => Container( width: 5 * u, height: h * u, decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(3 * u)), ); return SizedBox( width: size, height: size, child: Transform( alignment: Alignment.center, transform: Matrix4.skewX(-0.23), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ bar(18, DriverVault.brand700), SizedBox(width: 3.5 * u), bar(28, DriverVault.brand500), SizedBox(width: 3.5 * u), bar(36, DriverVault.brand400), ], ), ), ); } } /// Full DriverVault lockup: the mark + the Archivo italic-800 wordmark /// ("Driver" in strong ink, "Vault" in brand blue). class DriverVaultLogo extends StatelessWidget { final double markSize; final double fontSize; const DriverVaultLogo({super.key, this.markSize = 32, this.fontSize = 24}); @override Widget build(BuildContext context) { final strong = DriverVault.isDark(context) ? DriverVault.darkTextStrong : DriverVault.ink900; final brand = DriverVault.isDark(context) ? DriverVault.brand400 : DriverVault.brand700; return Row( mainAxisSize: MainAxisSize.min, children: [ DriverVaultMark(size: markSize), SizedBox(width: markSize * 0.32), Text.rich( TextSpan(children: [ TextSpan(text: "Driver", style: TextStyle(color: strong)), TextSpan(text: "Vault", style: TextStyle(color: brand)), ]), style: GoogleFonts.archivo( fontSize: fontSize, fontWeight: FontWeight.w800, fontStyle: FontStyle.italic, letterSpacing: -0.5, ), ), ], ); } }