Files
LearnIT/docs/UI_DESIGN_GUIDELINES.md

39 KiB

UI Design Guidelines - AI Study Assistant

🎨 DESIGN SYSTEM OVERVIEW

Core Design Philosophy

  • Clean & Modern: Minimalist interface with purposeful use of color and space
  • Educational Focus: UI elements designed to support learning, not distract
  • Accessibility First: High contrast ratios, readable fonts, and clear visual hierarchy
  • Responsive Design: Seamless experience across mobile, tablet, and web

🎨 COLOR PALETTE

Primary Colors (New EPVC Color Scheme)

class AppColors {
  // Primary Brand Colors
  static const Color primaryTeal = Color(0xFF82C9BD);      // Main teal color - PRIMARY
  static const Color primaryOrange = Color(0xFFF68D2D);     // Accent orange - SECONDARY
  
  // Gradient Colors
  static const Color gradientStart = Color(0xFF82C9BD);    // Teal gradient start
  static const Color gradientEnd = Color(0xFF6AB8A8);      // Darker teal gradient end
  
  // Secondary Colors
  static const Color secondaryTeal = Color(0xFF6AB8A8);     // Darker teal
  static const Color accentTeal = Color(0xFF5AA69A);        // Lighter teal accent
  static const Color lightOrange = Color(0xFFF7A960);       // Lighter orange
  
  // Neutral Colors
  static const Color background = Color(0xFFF8F9FA);        // Light gray background
  static const Color surface = Color(0xFFFFFFFF);          // White surfaces
  static const Color cardBackground = Color(0xFFFFFFFF);    // White cards
  
  // Text Colors
  static const Color textPrimary = Color(0xFF1A1A1A);       // Primary text
  static const Color textSecondary = Color(0xFF6B7280);     // Secondary text
  static const Color textHint = Color(0xFF9CA3AF);          // Hint text
  
  // Status Colors
  static const Color success = Color(0xFF10B981);           // Green for success
  static const Color warning = Color(0xFFF59E0B);           // Amber for warnings
  static const Color error = Color(0xFFEF4444);            // Red for errors
  static const Color info = Color(0xFF3B82F6);             // Blue for info
  
  // Interactive Colors
  static const Color buttonPrimary = Color(0xFF82C9BD);    // Primary button (teal)
  static const Color buttonAccent = Color(0xFFF68D2D);      // Accent button (orange)
  static const Color buttonSecondary = Color(0xFFE5E7EB);  // Secondary button
  static const Color iconActive = Color(0xFF82C9BD);       // Active icons (teal)
  static const Color iconInactive = Color(0xFF9CA3AF);     // Inactive icons
  
  // Chat Specific Colors
  static const Color chatBubbleStudent = Color(0xFF82C9BD); // Student messages (teal)
  static const Color chatBubbleAI = Color(0xFFF3F4F6);      // AI messages
  static const Color chatInputBackground = Color(0xFFF8F9FA); // Input background
  static const Color chatSendButton = Color(0xFF82C9BD);    // Send button (teal)
}

Color Usage Guidelines

Primary Colors (70% usage)

  • Primary Teal (#82C9BD): Main actions, navigation, important CTAs, backgrounds, frequent UI elements
  • Primary Orange (#F68D2D): Accent buttons, highlights, achievements, important notifications (used sparingly)
  • Secondary Teal: Hover states, secondary actions, supporting elements

Neutral Colors (25% usage)

  • Background: Page backgrounds, card backgrounds
  • Surface: Cards, modals, dropdowns
  • Text Colors: Hierarchical text organization

Status Colors (5% usage)

  • Success: Quiz completion, correct answers
  • Warning: Low mastery, upcoming deadlines
  • Error: Network errors, validation failures
  • Info: System notifications, help text

Color Distribution Strategy

  • Teal (#82C9BD): 60% of color usage - Primary brand color for backgrounds, main actions, navigation
  • Orange (#F68D2D): 10% of color usage - Accent color for CTAs, highlights, special buttons
  • Neutral Colors: 30% of color usage - Backgrounds, surfaces, text hierarchy

📝 TYPOGRAPHY

Font Family

class AppTypography {
  // Primary Font Family (System fonts for consistency)
  static const String primaryFont = 'SF Pro Display'; // iOS
  static const String secondaryFont = 'Roboto';      // Android
  static const String webFont = 'Inter';              // Web
  
  // Font Weights
  static const FontWeight thin = FontWeight.w100;
  static const FontWeight light = FontWeight.w300;
  static const FontWeight regular = FontWeight.w400;
  static const FontWeight medium = FontWeight.w500;
  static const FontWeight semiBold = FontWeight.w600;
  static const FontWeight bold = FontWeight.w700;
  static const FontWeight extraBold = FontWeight.w800;
}

Text Styles

class AppTextStyles {
  // Headings
  static const TextStyle h1 = TextStyle(
    fontSize: 32,
    fontWeight: FontWeight.bold,
    color: AppColors.textPrimary,
    height: 1.2,
  );
  
  static const TextStyle h2 = TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.semiBold,
    color: AppColors.textPrimary,
    height: 1.3,
  );
  
  static const TextStyle h3 = TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.semiBold,
    color: AppColors.textPrimary,
    height: 1.4,
  );
  
  // Body Text
  static const TextStyle bodyLarge = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.regular,
    color: AppColors.textPrimary,
    height: 1.5,
  );
  
  static const TextStyle bodyMedium = TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.regular,
    color: AppColors.textPrimary,
    height: 1.5,
  );
  
  static const TextStyle bodySmall = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.regular,
    color: AppColors.textSecondary,
    height: 1.4,
  );
  
  // Button Text
  static const TextStyle buttonLarge = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.semiBold,
    color: Colors.white,
    height: 1.2,
  );
  
  static const TextStyle buttonMedium = TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.medium,
    color: Colors.white,
    height: 1.2,
  );
  
  // Caption and Label
  static const TextStyle caption = TextStyle(
    fontSize: 11,
    fontWeight: FontWeight.regular,
    color: AppColors.textHint,
    height: 1.3,
  );
  
  static const TextStyle label = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.medium,
    color: AppColors.textSecondary,
    height: 1.3,
  );
}

🎯 ICONOGRAPHY

Icon Sets

  • Primary: Cupertino Icons (iOS-style consistency)
  • Secondary: Material Icons (for Android compatibility)
  • Custom: Educational icons for specific features

Icon Guidelines

  • Size: 24px for standard icons, 16px for compact, 32px for large
  • Weight: Medium (consistent across the app)
  • Color: Use iconActive for active states, iconInactive for inactive
  • Meaning: Clear, universally understood symbols

Key Icons

class AppIcons {
  // Navigation
  static const IconData home = CupertinoIcons.home;
  static const IconData search = CupertinoIcons.search;
  static const IconData profile = CupertinoIcons.person_circle;
  static const IconData settings = CupertinoIcons.settings;
  static const IconData menu = CupertinoIcons.bars;
  
  // Actions
  static const IconData send = CupertinoIcons.paperplane_fill;
  static const IconData add = CupertinoIcons.add;
  static const IconData edit = CupertinoIcons.pencil;
  static const IconData delete = CupertinoIcons.trash;
  static const IconData check = CupertinoIcons.check_mark;
  
  // Educational
  static const IconData book = CupertinoIcons.book;
  static const IconData quiz = CupertinoIcons.question_circle;
  static const IconData progress = CupertinoIcons.chart_bar;
  static const IconData lightbulb = CupertinoIcons.lightbulb;
  static const IconData trophy = CupertinoIcons.trophy;
  
  // Status
  static const IconData success = CupertinoIcons.check_mark_circled;
  static const IconData warning = CupertinoIcons.exclamationmark_triangle;
  static const IconData error = CupertinoIcons.xmark_circle;
  static const IconData info = CupertinoIcons.info_circle;
}

📱 COMPONENTS

Buttons

Primary Button

class PrimaryButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final bool isLoading;
  final bool isDisabled;
  
  const PrimaryButton({
    Key? key,
    required this.text,
    required this.onPressed,
    this.isLoading = false,
    this.isDisabled = false,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 200),
      height: 48,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: isDisabled 
            ? [AppColors.buttonSecondary, AppColors.buttonSecondary]
            : [AppColors.gradientStart, AppColors.gradientEnd],
        ),
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: AppColors.primaryBlue.withOpacity(0.2),
            blurRadius: 8,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          borderRadius: BorderRadius.circular(12),
          onTap: isDisabled || isLoading ? null : onPressed,
          child: Center(
            child: isLoading
              ? const SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(
                    strokeWidth: 2,
                    valueColor: AlwaysStoppedAnimation(Colors.white),
                  ),
                )
              : Text(
                  text,
                  style: AppTextStyles.buttonLarge,
                ),
          ),
        ),
      ),
    );
  }
}

Secondary Button

class SecondaryButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;
  final bool isDisabled;
  
  const SecondaryButton({
    Key? key,
    required this.text,
    required this.onPressed,
    this.isDisabled = false,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 48,
      decoration: BoxDecoration(
        color: AppColors.buttonSecondary,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
          color: AppColors.primaryBlue.withOpacity(0.3),
          width: 1,
        ),
      ),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          borderRadius: BorderRadius.circular(12),
          onTap: isDisabled ? null : onPressed,
          child: Center(
            child: Text(
              text,
              style: AppTextStyles.buttonLarge.copyWith(
                color: isDisabled 
                  ? AppColors.textHint 
                  : AppColors.primaryBlue,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Cards

Standard Card

class StandardCard extends StatelessWidget {
  final Widget child;
  final EdgeInsetsGeometry? padding;
  final EdgeInsetsGeometry? margin;
  final VoidCallback? onTap;
  final bool hasShadow;
  
  const StandardCard({
    required this.child,
    this.padding,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: AppColors.surface,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.08),
            blurRadius: 8,
            offset: Offset(0, 2),
          ),
          BoxShadow(
            color: Colors.black.withOpacity(0.04),
            blurRadius: 1,
            offset: Offset(0, 1),
          ),
        ],
      ),
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: onTap,
          borderRadius: BorderRadius.circular(16),
          child: Padding(
            padding: padding ?? EdgeInsets.all(16),
            child: child,
          ),
        ),
      ),
    );
  }
}

Interactive Card

class InteractiveCard extends StatefulWidget {
  final Widget child;
  final EdgeInsetsGeometry? padding;
  final VoidCallback? onTap;
  final bool isSelected;
  
  const InteractiveCard({
    required this.child,
    this.padding,
    this.onTap,
    this.isSelected = false,
  });

  @override
  State<InteractiveCard> createState() => _InteractiveCardState();
}

class _InteractiveCardState extends State<InteractiveCard> {
  bool isHovered = false;

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) => setState(() => isHovered = true),
      onExit: (_) => setState(() => isHovered = false),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 200),
        decoration: BoxDecoration(
          color: widget.isSelected 
            ? AppColors.primaryBlue.withOpacity(0.1)
            : AppColors.surface,
          borderRadius: BorderRadius.circular(16),
          border: widget.isSelected 
            ? Border.all(color: AppColors.primaryBlue, width: 2)
            : null,
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(isHovered ? 0.12 : 0.08),
              blurRadius: isHovered ? 12 : 8,
              offset: Offset(0, isHovered ? 4 : 2),
            ),
            BoxShadow(
              color: Colors.black.withOpacity(0.04),
              blurRadius: 1,
              offset: Offset(0, 1),
            ),
          ],
        ),
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: widget.onTap,
            borderRadius: BorderRadius.circular(16),
            child: Padding(
              padding: widget.padding ?? EdgeInsets.all(16),
              child: widget.child,
            ),
          ),
        ),
      ),
    );
  }
}

Input Fields

Text Input

class AppTextInput extends StatelessWidget {
  final String? label;
  final String? hint;
  final String? errorText;
  final TextEditingController controller;
  final bool obscureText;
  final TextInputType keyboardType;
  final VoidCallback? onTap;
  final ValueChanged<String>? onChanged;
  final bool isDisabled;
  
  const AppTextInput({
    Key? key,
    this.label,
    this.hint,
    this.errorText,
    required this.controller,
    this.obscureText = false,
    this.keyboardType = TextInputType.text,
    this.onTap,
    this.onChanged,
    this.isDisabled = false,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (label != null) ...[
          Text(
            label!,
            style: AppTextStyles.label,
          ),
          const SizedBox(height: 8),
        ],
        Container(
          decoration: BoxDecoration(
            color: isDisabled ? AppColors.buttonSecondary : AppColors.surface,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(
              color: errorText != null 
                ? AppColors.error 
                : AppColors.primaryBlue.withOpacity(0.3),
              width: 1,
            ),
          ),
          child: TextField(
            controller: controller,
            obscureText: obscureText,
            keyboardType: keyboardType,
            onTap: onTap,
            onChanged: onChanged,
            enabled: !isDisabled,
            decoration: InputDecoration(
              hintText: hint,
              hintStyle: AppTextStyles.bodyMedium.copyWith(
                color: AppColors.textHint,
              ),
              border: InputBorder.none,
              contentPadding: const EdgeInsets.all(16),
            ),
            style: AppTextStyles.bodyMedium,
          ),
        ),
        if (errorText != null) ...[
          const SizedBox(height: 4),
          Text(
            errorText!,
            style: AppTextStyles.caption.copyWith(
              color: AppColors.error,
            ),
          ),
        ],
      ],
    );
  }
}

📱 MODERN LAYOUT PATTERNS

Dashboard Layout

Based on the reference design, implement a modern card-based dashboard with:

Grid Layout System

class DashboardGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(16),
      child: GridView.count(
        crossAxisCount: 2,
        crossAxisSpacing: 16,
        mainAxisSpacing: 16,
        childAspectRatio: 1.2,
        children: [
          _buildQuickActionCard(
            icon: Icons.chat,
            title: "Ask Tutor",
            subtitle: "Get help with your questions",
            color: AppColors.primaryBlue,
            onTap: () => _navigateToChat(),
          ),
          _buildQuickActionCard(
            icon: Icons.quiz,
            title: "Take Quiz",
            subtitle: "Test your knowledge",
            color: AppColors.primaryTeal,
            onTap: () => _navigateToQuiz(),
          ),
          _buildQuickActionCard(
            icon: Icons.trending_up,
            title: "Progress",
            subtitle: "Track your learning",
            color: AppColors.primaryOrange,
            onTap: () => _navigateToProgress(),
          ),
          _buildQuickActionCard(
            icon: Icons.book,
            title: "Study Materials",
            subtitle: "Access learning resources",
            color: AppColors.secondaryBlue,
            onTap: () => _navigateToMaterials(),
          ),
        ],
      ),
    );
  }
  
  Widget _buildQuickActionCard({
    required IconData icon,
    required String title,
    required String subtitle,
    required Color color,
    required VoidCallback onTap,
  }) {
    return InteractiveCard(
      onTap: onTap,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: 48,
            height: 48,
            decoration: BoxDecoration(
              color: color.withOpacity(0.1),
              borderRadius: BorderRadius.circular(12),
            ),
            child: Icon(
              icon,
              color: color,
              size: 24,
            ),
          ),
          SizedBox(height: 12),
          Text(
            title,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w600,
              color: AppColors.textPrimary,
            ),
          ),
          SizedBox(height: 4),
          Text(
            subtitle,
            style: TextStyle(
              fontSize: 12,
              color: AppColors.textSecondary,
            ),
          ),
        ],
      ),
    );
  }
}

Content Cards

class ContentCard extends StatelessWidget {
  final String title;
  final String description;
  final String progress;
  final Color accentColor;
  final VoidCallback onTap;
  
  const ContentCard({
    required this.title,
    required this.description,
    required this.progress,
    required this.accentColor,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return InteractiveCard(
      onTap: onTap,
      padding: EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                        color: AppColors.textPrimary,
                      ),
                    ),
                    SizedBox(height: 8),
                    Text(
                      description,
                      style: TextStyle(
                        fontSize: 14,
                        color: AppColors.textSecondary,
                      ),
                    ),
                  ],
                ),
              ),
              Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  color: accentColor.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Icon(
                  Icons.arrow_forward,
                  color: accentColor,
                  size: 20,
                ),
              ),
            ],
          ),
          SizedBox(height: 16),
          Container(
            height: 4,
            decoration: BoxDecoration(
              color: AppColors.buttonSecondary,
              borderRadius: BorderRadius.circular(2),
            ),
            child: FractionallySizedBox(
              alignment: Alignment.centerLeft,
              widthFactor: double.parse(progress),
              child: Container(
                decoration: BoxDecoration(
                  color: accentColor,
                  borderRadius: BorderRadius.circular(2),
                ),
              ),
            ),
          ),
          SizedBox(height: 8),
          Text(
            '${(double.parse(progress) * 100).toInt()}% Complete',
            style: TextStyle(
              fontSize: 12,
              color: AppColors.textHint,
            ),
          ),
        ],
      ),
    );
  }
}

Navigation Bar

class ModernBottomNavigation extends StatelessWidget {
  final int currentIndex;
  final ValueChanged<int> onTap;
  
  const ModernBottomNavigation({
    required this.currentIndex,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: AppColors.surface,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 10,
            offset: Offset(0, -2),
          ),
        ],
      ),
      child: SafeArea(
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildNavItem(
                icon: Icons.home,
                label: "Home",
                index: 0,
              ),
              _buildNavItem(
                icon: Icons.chat,
                label: "Chat",
                index: 1,
              ),
              _buildNavItem(
                icon: Icons.quiz,
                label: "Quiz",
                index: 2,
              ),
              _buildNavItem(
                icon: Icons.person,
                label: "Profile",
                index: 3,
              ),
            ],
          ),
        ),
      ),
    );
  }
  
  Widget _buildNavItem({
    required IconData icon,
    required String label,
    required int index,
  }) {
    final isActive = currentIndex == index;
    final color = isActive ? AppColors.primaryBlue : AppColors.iconInactive;
    
    return GestureDetector(
      onTap: () => onTap(index),
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        decoration: BoxDecoration(
          color: isActive ? AppColors.primaryBlue.withOpacity(0.1) : Colors.transparent,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              icon,
              color: color,
              size: 24,
            ),
            SizedBox(height: 4),
            Text(
              label,
              style: TextStyle(
                color: color,
                fontSize: 12,
                fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Chat Interface

class ModernChatInterface extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Chat Header
        Container(
          padding: EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: AppColors.surface,
            border: Border(
              bottom: BorderSide(
                color: AppColors.buttonSecondary,
                width: 1,
              ),
            ),
          ),
          child: Row(
            children: [
              CircleAvatar(
                backgroundColor: AppColors.primaryBlue.withOpacity(0.1),
                child: Icon(
                  Icons.smart_toy,
                  color: AppColors.primaryBlue,
                ),
              ),
              SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      "AI Tutor",
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                        color: AppColors.textPrimary,
                      ),
                    ),
                    Text(
                      "Always here to help",
                      style: TextStyle(
                        fontSize: 12,
                        color: AppColors.textSecondary,
                      ),
                    ),
                  ],
                ),
              ),
              Icon(
                Icons.more_vert,
                color: AppColors.iconInactive,
              ),
            ],
          ),
        ),
        
        // Chat Messages
        Expanded(
          child: Container(
            color: AppColors.background,
            child: ListView.builder(
              padding: EdgeInsets.all(16),
              itemCount: messages.length,
              itemBuilder: (context, index) {
                final message = messages[index];
                return _buildMessageBubble(message);
              },
            ),
          ),
        ),
        
        // Chat Input
        Container(
          padding: EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: AppColors.surface,
            border: Border(
              top: BorderSide(
                color: AppColors.buttonSecondary,
                width: 1,
              ),
            ),
          ),
          child: Row(
            children: [
              Expanded(
                child: Container(
                  decoration: BoxDecoration(
                    color: AppColors.chatInputBackground,
                    borderRadius: BorderRadius.circular(24),
                    border: Border.all(
                      color: AppColors.primaryBlue.withOpacity(0.3),
                      width: 1,
                    ),
                  ),
                  child: TextField(
                    decoration: InputDecoration(
                      hintText: "Ask your question...",
                      hintStyle: TextStyle(
                        color: AppColors.textHint,
                      ),
                      border: InputBorder.none,
                      contentPadding: EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 12,
                      ),
                    ),
                  ),
                ),
              ),
              SizedBox(width: 12),
              Container(
                width: 48,
                height: 48,
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [AppColors.primaryBlue, AppColors.primaryTeal],
                    begin: Alignment.centerLeft,
                    end: Alignment.centerRight,
                  ),
                  borderRadius: BorderRadius.circular(24),
                ),
                child: Icon(
                  Icons.send,
                  color: Colors.white,
                  size: 20,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
  
  Widget _buildMessageBubble(Message message) {
    final isUser = message.isUser;
    return Container(
      margin: EdgeInsets.only(bottom: 16),
      child: Row(
        mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
        children: [
          if (!isUser) ...[
            CircleAvatar(
              radius: 16,
              backgroundColor: AppColors.primaryBlue.withOpacity(0.1),
              child: Icon(
                Icons.smart_toy,
                color: AppColors.primaryBlue,
                size: 16,
              ),
            ),
            SizedBox(width: 8),
          ],
          Flexible(
            child: Container(
              constraints: BoxConstraints(
                maxWidth: MediaQuery.of(context).size.width * 0.7,
              ),
              padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
              decoration: BoxDecoration(
                color: isUser ? AppColors.primaryBlue : AppColors.chatBubbleAI,
                borderRadius: BorderRadius.circular(16),
              ),
              child: Text(
                message.text,
                style: TextStyle(
                  color: isUser ? Colors.white : AppColors.textPrimary,
                  fontSize: 14,
                ),
              ),
            ),
          ),
          if (isUser) ...[
            SizedBox(width: 8),
            CircleAvatar(
              radius: 16,
              backgroundColor: AppColors.primaryOrange.withOpacity(0.1),
              child: Icon(
                Icons.person,
                color: AppColors.primaryOrange,
                size: 16,
              ),
            ),
          ],
        ],
      ),
    );
  }
}

🎭 ANIMATIONS

Animation Principles

  • Purposeful: Every animation should enhance user understanding
  • Smooth: Use easing functions that feel natural
  • Fast: Respect user time, keep animations under 300ms
  • Consistent: Use similar timing across the app

Standard Animation Durations

class AppAnimations {
  static const Duration fast = Duration(milliseconds: 150);
  static const Duration medium = Duration(milliseconds: 250);
  static const Duration slow = Duration(milliseconds: 350);
  static const Duration extraSlow = Duration(milliseconds: 500);
}

Curves

class AppCurves {
  static const Curve easeIn = Curves.easeIn;
  static const Curve easeOut = Curves.easeOut;
  static const Curve easeInOut = Curves.easeInOut;
  static const Curve bounceIn = Curves.bounceIn;
  static const Curve elasticOut = Curves.elasticOut;
}

Common Animations

Fade Transition

class FadeIn extends StatefulWidget {
  final Widget child;
  final Duration duration;
  
  const FadeIn({
    Key? key,
    required this.child,
    this.duration = AppAnimations.medium,
  }) : super(key: key);
  
  @override
  State<FadeIn> createState() => _FadeInState();
}

class _FadeInState extends State<FadeIn> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
    );
    _animation = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: AppCurves.easeIn,
    ));
    
    _controller.forward();
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: widget.child,
    );
  }
}

Slide Transition

class SlideIn extends StatefulWidget {
  final Widget child;
  final Duration duration;
  final Offset begin;
  final Offset end;
  
  const SlideIn({
    Key? key,
    required this.child,
    this.duration = AppAnimations.medium,
    this.begin = const Offset(0, 0.3),
    this.end = Offset.zero,
  }) : super(key: key);
  
  @override
  State<SlideIn> createState() => _SlideInState();
}

class _SlideInState extends State<SlideIn> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _animation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
    );
    _animation = Tween<Offset>(
      begin: widget.begin,
      end: widget.end,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: AppCurves.easeOut,
    ));
    
    _controller.forward();
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: _animation,
      child: widget.child,
    );
  }
}

Scale Animation

class ScaleIn extends StatefulWidget {
  final Widget child;
  final Duration duration;
  
  const ScaleIn({
    Key? key,
    required this.child,
    this.duration = AppAnimations.medium,
  }) : super(key: key);
  
  @override
  State<ScaleIn> createState() => _ScaleInState();
}

class _ScaleInState extends State<ScaleIn> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
    );
    _animation = Tween<double>(
      begin: 0.8,
      end: 1.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: AppCurves.elasticOut,
    ));
    
    _controller.forward();
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: _animation,
      child: widget.child,
    );
  }
}

📐 LAYOUT GUIDELINES

Spacing System

class AppSpacing {
  // Base spacing unit (8px)
  static const double xs = 4;    // 0.5x
  static const double sm = 8;    // 1x
  static const double md = 16;   // 2x
  static const double lg = 24;   // 3x
  static const double xl = 32;   // 4x
  static const double xxl = 48;  // 6x
  static const double xxxl = 64; // 8x
}

Border Radius

class AppBorderRadius {
  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 round = 1000; // For circles
}

Screen Breakpoints

class AppBreakpoints {
  static const double mobile = 480;
  static const double tablet = 768;
  static const double desktop = 1024;
  static const double largeDesktop = 1440;
}

🎨 SCREEN-SPECIFIC DESIGNS

Login Screen

  • Background: Gradient from gradientStart to gradientEnd
  • Logo: Centered with fade-in animation
  • Form: White card with subtle shadow
  • Buttons: Primary gradient button for login, secondary for signup

Student Dashboard

  • Header: App bar with gradient background
  • Cards: Grid layout with mastery progress bars
  • Navigation: Bottom navigation with active state indicators
  • Animations: Cards slide in from bottom on load

Chat Interface

  • Background: Light gray (chatInputBackground)
  • Messages: Different colors for student vs AI
  • Input: Rounded input field with send button
  • Animations: Messages slide in from sides

Quiz Screen

  • Timer: Circular progress indicator
  • Questions: Card-based layout
  • Options: Radio buttons with hover states
  • Progress: Linear progress bar at top

🌙 DARK MODE

Dark Color Palette

class AppDarkColors {
  // Background Colors
  static const Color background = Color(0xFF121212);
  static const Color surface = Color(0xFF1E1E1E);
  static const Color cardBackground = Color(0xFF2D2D2D);
  
  // Text Colors
  static const Color textPrimary = Color(0xFFFFFFFF);
  static const Color textSecondary = Color(0xFFB3B3B3);
  static const Color textHint = Color(0xFF808080);
  
  // Keep primary colors the same
  static const Color primaryBlue = AppColors.primaryBlue;
  static const Color primaryTeal = AppColors.primaryTeal;
  static const Color primaryOrange = AppColors.primaryOrange;
}

Implementation Guidelines

  • Automatic: Follow system dark mode preference
  • Manual: Toggle in app settings
  • Consistency: All screens must support both themes
  • Animation: Smooth transition between themes

📱 RESPONSIVE DESIGN

Mobile (< 768px)

  • Layout: Single column, scrollable
  • Navigation: Bottom navigation bar
  • Cards: Full width with horizontal padding
  • Typography: Base sizes as defined

Tablet (768px - 1024px)

  • Layout: Two-column where appropriate
  • Navigation: Side drawer or top navigation
  • Cards: Grid layout (2-3 columns)
  • Typography: Slightly larger base sizes

Desktop (> 1024px)

  • Layout: Multi-column, fixed width centered
  • Navigation: Top navigation bar
  • Cards: Grid layout (3-4 columns)
  • Typography: Larger base sizes, better readability

🎯 ACCESSIBILITY

Color Contrast

  • Normal Text: Minimum 4.5:1 contrast ratio
  • Large Text: Minimum 3:1 contrast ratio
  • Interactive Elements: Minimum 3:1 contrast ratio

Typography

  • Minimum Size: 14px for body text
  • Line Height: 1.5 for better readability
  • Font Weight: Regular or medium for body text

Focus States

  • Visible: All interactive elements have visible focus states
  • Consistent: Same focus style across the app
  • Keyboard: Full keyboard navigation support

Screen Reader Support

  • Labels: All images and icons have alt text
  • Semantic: Use semantic HTML elements
  • Announcements: Important changes are announced

🚀 PERFORMANCE

Animation Performance

  • 60 FPS: All animations should maintain 60 FPS
  • GPU: Use GPU-accelerated animations where possible
  • Simplify: Avoid complex animations on low-end devices

Asset Optimization

  • Images: WebP format with appropriate sizes
  • Icons: SVG icons where possible
  • Fonts: Subset fonts to reduce size

Bundle Size

  • Tree Shaking: Remove unused code
  • Lazy Loading: Load features on demand
  • Compression: Compress all assets

📋 DESIGN CHECKLIST

Before Implementation

  • Color palette defined and consistent
  • Typography scale established
  • Component library created
  • Animation system implemented
  • Accessibility guidelines reviewed

During Implementation

  • Components follow design system
  • Animations are smooth and purposeful
  • Responsive design works on all breakpoints
  • Dark mode is supported
  • Accessibility features are implemented

Before Release

  • All screens reviewed for consistency
  • Performance tested on target devices
  • Accessibility tested with screen readers
  • User testing completed
  • Design documentation updated

🔄 DESIGN SYSTEM MAINTENANCE

Regular Updates

  • Monthly: Review usage analytics and user feedback
  • Quarterly: Update components and add new ones
  • Annually: Major design system updates

Version Control

  • Semantic Versioning: Use semantic versioning for releases
  • Changelog: Maintain detailed changelog
  • Migration: Provide migration guides for breaking changes

Documentation

  • Living Document: Keep documentation up to date
  • Examples: Provide code examples for all components
  • Guidelines: Maintain usage guidelines and best practices

Last Updated: 2026-05-06 Version: 1.0.0 Design System Owner: UI/UX Team