import 'package:flutter/material.dart'; /// Design tokens for DayMaker — a single source of truth for the new look. class AppColors { // Brand static const Color primary = Color(0xFF2563EB); // refined modern blue static const Color primaryDark = Color(0xFF1D4ED8); static const Color primaryLight = Color(0xFF60A5FA); static const Color accent = Color(0xFFFB923C); // warm orange accent (legacy peach reborn) // Neutrals static const Color background = Color(0xFFF7F8FB); static const Color surface = Colors.white; static const Color surfaceAlt = Color(0xFFF1F5F9); static const Color border = Color(0xFFE2E8F0); static const Color divider = Color(0xFFEDF2F7); // Text static const Color textPrimary = Color(0xFF0F172A); static const Color textSecondary = Color(0xFF64748B); static const Color textTertiary = Color(0xFF94A3B8); static const Color textOnPrimary = Colors.white; // Status static const Color success = Color(0xFF10B981); static const Color error = Color(0xFFEF4444); static const Color warning = Color(0xFFF59E0B); // Gradients static const LinearGradient brandGradient = LinearGradient( colors: [Color(0xFF2563EB), Color(0xFF60A5FA)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); static const LinearGradient warmGradient = LinearGradient( colors: [Color(0xFFFB923C), Color(0xFFF472B6)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); } class AppRadius { static const double sm = 8; static const double md = 12; static const double lg = 16; static const double xl = 20; static const double pill = 999; } class AppSpacing { static const double xs = 4; static const double sm = 8; static const double md = 12; static const double lg = 16; static const double xl = 20; static const double xxl = 24; static const double huge = 32; } class AppShadows { static List soft = [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 12, offset: const Offset(0, 4), ), ]; static List medium = [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 20, offset: const Offset(0, 8), ), ]; static List brand = [ BoxShadow( color: AppColors.primary.withValues(alpha: 0.25), blurRadius: 16, offset: const Offset(0, 8), ), ]; } class AppText { static const TextStyle h1 = TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: AppColors.textPrimary, height: 1.2, ); static const TextStyle h2 = TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: AppColors.textPrimary, height: 1.25, ); static const TextStyle h3 = TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.textPrimary, height: 1.3, ); static const TextStyle body = TextStyle( fontSize: 15, color: AppColors.textPrimary, height: 1.4, ); static const TextStyle bodySecondary = TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.4, ); static const TextStyle caption = TextStyle( fontSize: 12, color: AppColors.textTertiary, height: 1.3, ); static const TextStyle button = TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, letterSpacing: 0.2, ); static const TextStyle label = TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textSecondary, letterSpacing: 0.3, ); } /// Shared decorations for cards and surfaces. class AppDecorations { static BoxDecoration card({Color? color, double radius = AppRadius.lg}) => BoxDecoration( color: color ?? AppColors.surface, borderRadius: BorderRadius.circular(radius), boxShadow: AppShadows.soft, ); static BoxDecoration outlined({double radius = AppRadius.lg}) => BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(radius), border: Border.all(color: AppColors.border), ); static BoxDecoration filled({Color? color, double radius = AppRadius.md}) => BoxDecoration( color: color ?? AppColors.surfaceAlt, borderRadius: BorderRadius.circular(radius), ); } /// Reusable widgets. class AppButton extends StatelessWidget { final String label; final IconData? icon; final VoidCallback? onPressed; final bool loading; final bool secondary; final bool danger; final double height; const AppButton({ super.key, required this.label, this.icon, this.onPressed, this.loading = false, this.secondary = false, this.danger = false, this.height = 54, }); @override Widget build(BuildContext context) { final disabled = onPressed == null || loading; final bg = danger ? AppColors.error : secondary ? AppColors.surface : AppColors.primary; final fg = secondary ? AppColors.primary : Colors.white; return SizedBox( width: double.infinity, height: height, child: AnimatedOpacity( opacity: disabled && !loading ? 0.5 : 1, duration: const Duration(milliseconds: 150), child: Material( color: bg, borderRadius: BorderRadius.circular(AppRadius.lg), elevation: secondary ? 0 : 0, child: InkWell( onTap: disabled ? null : onPressed, borderRadius: BorderRadius.circular(AppRadius.lg), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppRadius.lg), border: secondary ? Border.all(color: AppColors.primary, width: 1.5) : null, boxShadow: secondary || danger ? null : AppShadows.brand, ), alignment: Alignment.center, child: loading ? SizedBox( height: 22, width: 22, child: CircularProgressIndicator( strokeWidth: 2.4, valueColor: AlwaysStoppedAnimation(fg), ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) ...[ Icon(icon, color: fg, size: 20), const SizedBox(width: 8), ], Text( label, style: AppText.button.copyWith(color: fg), ), ], ), ), ), ), ), ); } } /// Pill-style chip for filters/tags. Animated selection. class AppChip extends StatelessWidget { final String label; final IconData? icon; final bool selected; final VoidCallback? onTap; final Color? color; const AppChip({ super.key, required this.label, this.icon, this.selected = false, this.onTap, this.color, }); @override Widget build(BuildContext context) { final accent = color ?? AppColors.primary; return AnimatedContainer( duration: const Duration(milliseconds: 180), curve: Curves.easeOut, decoration: BoxDecoration( color: selected ? accent : AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.pill), border: Border.all( color: selected ? accent : AppColors.border, width: 1.2, ), boxShadow: selected ? [ BoxShadow( color: accent.withValues(alpha: 0.25), blurRadius: 10, offset: const Offset(0, 4), ) ] : null, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.pill), onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 8, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (icon != null) ...[ Icon( icon, size: 16, color: selected ? Colors.white : accent, ), const SizedBox(width: 6), ], Text( label, style: TextStyle( fontSize: 13.5, fontWeight: FontWeight.w600, color: selected ? Colors.white : AppColors.textPrimary, ), ), ], ), ), ), ), ); } } /// Helpers for snackbars class AppSnack { static void error(BuildContext context, String message) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.error_outline, color: Colors.white), const SizedBox(width: 10), Expanded(child: Text(message)), ], ), backgroundColor: AppColors.error, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.md), ), ), ); } static void success(BuildContext context, String message) { ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle_outline, color: Colors.white), const SizedBox(width: 10), Expanded(child: Text(message)), ], ), backgroundColor: AppColors.success, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.md), ), ), ); } }