import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; abstract final class AppColors { // Primary — professional navy static const primary = Color(0xFF1E3A5F); static const onPrimary = Color(0xFFFFFFFF); // Accent — sky blue CTA static const accent = Color(0xFF0369A1); static const onAccent = Color(0xFFFFFFFF); // Status static const pending = Color(0xFFF59E0B); static const pendingBg = Color(0xFFFFFBEB); static const inProgress = Color(0xFF0369A1); static const inProgressBg = Color(0xFFEFF6FF); static const success = Color(0xFF059669); static const successBg = Color(0xFFECFDF5); static const cancelled = Color(0xFFDC2626); static const cancelledBg = Color(0xFFFEF2F2); // Surfaces static const background = Color(0xFFF1F5F9); static const surface = Color(0xFFFFFFFF); static const surfaceVariant = Color(0xFFF8FAFC); static const muted = Color(0xFFE2E8F0); static const border = Color(0xFFE2E8F0); // Text static const textPrimary = Color(0xFF0F172A); static const textSecondary = Color(0xFF64748B); static const textMuted = Color(0xFF94A3B8); // Dark variants static const darkBackground = Color(0xFF0F172A); static const darkSurface = Color(0xFF1E293B); static const darkSurfaceVariant = Color(0xFF273344); static const darkBorder = Color(0xFF334155); static const darkTextPrimary = Color(0xFFF1F5F9); static const darkTextSecondary = Color(0xFF94A3B8); } abstract final class AppLayout { /// Window width above which the sidebar navigation is shown instead of bottom nav. static const double sidebarBreakpoint = 720.0; /// Window width above which wide-desktop content layouts activate /// (e.g., 3-column stat card row, 2-column forms). static const double wideBreakpoint = 1100.0; /// Maximum content width used for dashboard horizontal padding. static const double contentMaxWidth = 1040.0; } abstract final class AppTheme { static TextTheme _buildTextTheme(Color bodyColor, Color displayColor) { final base = GoogleFonts.plusJakartaSansTextTheme(); return base.copyWith( displayLarge: base.displayLarge?.copyWith(color: displayColor, fontWeight: FontWeight.w800), displayMedium: base.displayMedium?.copyWith(color: displayColor, fontWeight: FontWeight.w700), headlineLarge: base.headlineLarge?.copyWith(color: displayColor, fontWeight: FontWeight.w700), headlineMedium: base.headlineMedium?.copyWith(color: displayColor, fontWeight: FontWeight.w700), headlineSmall: base.headlineSmall?.copyWith(color: displayColor, fontWeight: FontWeight.w600), titleLarge: base.titleLarge?.copyWith(color: displayColor, fontWeight: FontWeight.w600), titleMedium: base.titleMedium?.copyWith(color: displayColor, fontWeight: FontWeight.w600), titleSmall: base.titleSmall?.copyWith(color: displayColor, fontWeight: FontWeight.w500), bodyLarge: base.bodyLarge?.copyWith(color: bodyColor), bodyMedium: base.bodyMedium?.copyWith(color: bodyColor), bodySmall: base.bodySmall?.copyWith(color: AppColors.textSecondary), labelLarge: base.labelLarge?.copyWith(fontWeight: FontWeight.w600), labelMedium: base.labelMedium?.copyWith(fontWeight: FontWeight.w500), ); } static final light = ThemeData( useMaterial3: true, colorScheme: ColorScheme( brightness: Brightness.light, primary: AppColors.primary, onPrimary: AppColors.onPrimary, primaryContainer: const Color(0xFFDBEAFE), onPrimaryContainer: AppColors.primary, secondary: AppColors.accent, onSecondary: AppColors.onAccent, secondaryContainer: const Color(0xFFE0F2FE), onSecondaryContainer: AppColors.accent, tertiary: AppColors.success, onTertiary: Colors.white, tertiaryContainer: AppColors.successBg, onTertiaryContainer: AppColors.success, error: AppColors.cancelled, onError: Colors.white, errorContainer: AppColors.cancelledBg, onErrorContainer: AppColors.cancelled, surface: AppColors.surface, onSurface: AppColors.textPrimary, surfaceContainerHighest: AppColors.surfaceVariant, onSurfaceVariant: AppColors.textSecondary, outline: AppColors.border, outlineVariant: AppColors.muted, scrim: Colors.black54, inverseSurface: AppColors.darkSurface, onInverseSurface: AppColors.darkTextPrimary, inversePrimary: const Color(0xFF93C5FD), ), scaffoldBackgroundColor: AppColors.background, textTheme: _buildTextTheme(AppColors.textPrimary, AppColors.textPrimary), appBarTheme: AppBarTheme( backgroundColor: AppColors.surface, foregroundColor: AppColors.textPrimary, surfaceTintColor: Colors.transparent, elevation: 0, scrolledUnderElevation: 0, shadowColor: Colors.transparent, centerTitle: false, systemOverlayStyle: SystemUiOverlayStyle.dark, titleTextStyle: GoogleFonts.plusJakartaSans( fontSize: 17, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), iconTheme: const IconThemeData(color: AppColors.textPrimary, size: 22), ), cardTheme: CardThemeData( elevation: 0, color: AppColors.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide(color: AppColors.border, width: 1), ), margin: EdgeInsets.zero, clipBehavior: Clip.antiAlias, ), navigationBarTheme: NavigationBarThemeData( backgroundColor: AppColors.surface, elevation: 0, shadowColor: Colors.transparent, indicatorColor: const Color(0xFFDBEAFE), iconTheme: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return const IconThemeData(color: AppColors.primary, size: 22); } return IconThemeData(color: AppColors.textSecondary, size: 22); }), labelTextStyle: WidgetStateProperty.resolveWith((states) { final style = GoogleFonts.plusJakartaSans(fontSize: 11); if (states.contains(WidgetState.selected)) { return style.copyWith(fontWeight: FontWeight.w600, color: AppColors.primary); } return style.copyWith(fontWeight: FontWeight.w500, color: AppColors.textSecondary); }), surfaceTintColor: Colors.transparent, ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: AppColors.onPrimary, minimumSize: const Size(0, 48), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), textStyle: GoogleFonts.plusJakartaSans(fontSize: 15, fontWeight: FontWeight.w600), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, minimumSize: const Size(0, 48), side: const BorderSide(color: AppColors.border, width: 1.5), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), textStyle: GoogleFonts.plusJakartaSans(fontSize: 15, fontWeight: FontWeight.w600), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColors.surfaceVariant, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: AppColors.accent, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: AppColors.cancelled, width: 1.5), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), labelStyle: GoogleFonts.plusJakartaSans(color: AppColors.textSecondary), hintStyle: GoogleFonts.plusJakartaSans(color: AppColors.textMuted), ), chipTheme: ChipThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), side: BorderSide.none, ), dividerTheme: const DividerThemeData( color: AppColors.border, thickness: 1, space: 1, ), listTileTheme: const ListTileThemeData( contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), ), ); static final dark = ThemeData( useMaterial3: true, colorScheme: ColorScheme( brightness: Brightness.dark, primary: const Color(0xFF93C5FD), onPrimary: const Color(0xFF1E3A5F), primaryContainer: const Color(0xFF1E3A5F), onPrimaryContainer: const Color(0xFFDBEAFE), secondary: const Color(0xFF7DD3FC), onSecondary: const Color(0xFF0C4A6E), secondaryContainer: const Color(0xFF0C4A6E), onSecondaryContainer: const Color(0xFFE0F2FE), tertiary: const Color(0xFF6EE7B7), onTertiary: const Color(0xFF064E3B), tertiaryContainer: const Color(0xFF064E3B), onTertiaryContainer: const Color(0xFFD1FAE5), error: const Color(0xFFFCA5A5), onError: const Color(0xFF7F1D1D), errorContainer: const Color(0xFF7F1D1D), onErrorContainer: const Color(0xFFFEE2E2), surface: AppColors.darkSurface, onSurface: AppColors.darkTextPrimary, surfaceContainerHighest: AppColors.darkSurfaceVariant, onSurfaceVariant: AppColors.darkTextSecondary, outline: AppColors.darkBorder, outlineVariant: const Color(0xFF1E293B), scrim: Colors.black87, inverseSurface: const Color(0xFFF1F5F9), onInverseSurface: AppColors.textPrimary, inversePrimary: AppColors.primary, ), scaffoldBackgroundColor: AppColors.darkBackground, textTheme: _buildTextTheme(AppColors.darkTextPrimary, AppColors.darkTextPrimary), appBarTheme: AppBarTheme( backgroundColor: AppColors.darkSurface, foregroundColor: AppColors.darkTextPrimary, elevation: 0, scrolledUnderElevation: 1, systemOverlayStyle: SystemUiOverlayStyle.light, titleTextStyle: GoogleFonts.plusJakartaSans( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.darkTextPrimary, ), ), cardTheme: CardThemeData( elevation: 0, color: AppColors.darkSurface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: const BorderSide(color: AppColors.darkBorder, width: 1), ), margin: EdgeInsets.zero, clipBehavior: Clip.antiAlias, ), navigationBarTheme: NavigationBarThemeData( backgroundColor: AppColors.darkSurface, elevation: 0, indicatorColor: const Color(0xFF1E3A5F), iconTheme: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return const IconThemeData(color: Color(0xFF93C5FD), size: 22); } return IconThemeData(color: AppColors.darkTextSecondary, size: 22); }), labelTextStyle: WidgetStateProperty.resolveWith((states) { final style = GoogleFonts.plusJakartaSans(fontSize: 11); if (states.contains(WidgetState.selected)) { return style.copyWith(fontWeight: FontWeight.w600, color: const Color(0xFF93C5FD)); } return style.copyWith(fontWeight: FontWeight.w500, color: AppColors.darkTextSecondary); }), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: const Color(0xFF93C5FD), foregroundColor: const Color(0xFF1E3A5F), minimumSize: const Size(0, 48), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), textStyle: GoogleFonts.plusJakartaSans(fontSize: 15, fontWeight: FontWeight.w600), ), ), dividerTheme: const DividerThemeData( color: AppColors.darkBorder, thickness: 1, space: 1, ), ); }