Hey Q! Add A New Feature To My Blazor App

Hey Q! Add A New Feature To My Blazor App

Welcome to the Hey Q! blog series where we build fun apps using Amazon Q Developer and learn about the code along the way! The scope of the sample apps created in this series are small 1-2 page applications that are fun, well designed and properly architected following best practices. And, the best part, the code is all open-source allowing you to grab it and use it for your apps! Here's the link to the completed open-source project: https://github.com/TheCodeTraveler/TaskQuest

This week we are updating an existing web app built using Blazor Server. For this app update, I have a few goals:

1. The app must compile and run on in a browser

2. The app must have a nice, highly polished, professional User Interface (UI)

3. The code must use the MVVM Architecture

4. The code must follow best practices

5. The code must use the most-recent versions for all dependencies

The Completed App

Note: If you're interested in learning more about prompt engineering and want to see the steps I used to guide Q Developer CLI, scroll down to the Appendix.

The new feature looks great! Here's a screen recording:

I hadn't thought about adding a category feature; Q Dev came up with that on its own. However, adding "Quest Categories" is a great idea! It helps us to prioritize each task and provides us the ability to sort/filter by category.

Along with being a great new feature, the implementation is wonderful! The updated UI seamlessly blends into the existing UX continuing to provide a highly-polished design. And along with a beautiful UI, all of the functionality (e.g. buttons, drop down menus, etc) all work without needing me to intervene and update Q Developer CLI's code.

Things to Watch Out For

When adding this new feature, there were two implementation details that I asked Q Developer CLI to not do:

  • Do not implement an ICategoryService interface.
  • Do not return a Task from a method when async/await is not being used

Initially, Q Developer offered to generate this interface for CategoryService, however, this was an unnecessary layer of abstraction that didn't add value to the existing app. One day an interface may be necessary, like when we add Unit Tests, however, it is a bit of over-engineering today.

The interface also created a contract where every method returned a Task, however, in the implementation of CategoryService, async/await is not being used.

Thankfully, after giving Q Developer CLI clear instructions to avoid using an interface when not required and to avoid returning a Task when async/await is not being used, it corrected its provided code and we were back on track!

Appendix

For anyone out there who is interested in learning more about prompt engineering, this section contains the steps I followed to create this app.

Note: Responses from LLMs are non-deterministic which can result in different outputs despite asking the same question. You may receive slightly different responses from Q Developer when trying to replicate the same steps below.


I've already installed Q Developer CLI on my Mac, so let's begin by opening the macOS Terminal and begin chatting with Q using the following command:

> q chat

Next, I will help Q out a bit by giving Q a profile. This reduces the scope of the datasets that Q will reference helps Q better understand the problem we are trying to solve together:

Prompt

Hey Q! You are a senior Blazor Developer who is an expert in C# and always follows C# and Web best practices. You always reference the official C# documentation on https://learn.microsoft.com/dotnet/csharp/ when writing your code and you always reference the official Blazor documentation when writing your code https://learn.microsoft.com/aspnet/core/blazor/. You always write code that uses the recommended libraries, like .NET Aspire (https://www.nuget.org/profiles/aspire) and CommunityToolkit.Mvvm (https://www.nuget.org/packages/CommunityToolkit.Mvvm). The User Interface for your apps are always highly polished and professional, giving the appearance that it was created by a Senior Designer. In addition to the reference domains above, you are also allowed to reference the open-source NET runtime code on GitHub, https://github.com/dotnet/runtime, andthe open-source .NET MAUI code on GitHub https://github.com/dotnet/maui.

Done! Now let's ask it to start building us an app and see what happens!

Prompt

In this folder is an existing web app built using Blazor called TaskQuest. Please add a new feature to the existing app.

Response

Perfect! Now I have a good understanding of the TaskQuest application. It's a well-structured Blazor app with gamified task management. Let me suggest and implement a compelling new feature that would enhance the user experience.
Based on the current functionality, I'll add a Task Categories/Tags feature that allows users to organize their tasks into different categories (like Work, Personal, Learning, etc.) with color-coded visual indicators. This will make the app more organized and visually appealing.
Here's what I'll implement:
## New Feature: Task Categories with Visual Organization
Features to add:
1. Category Model - Add categories to tasks
2. Category Management - Create, edit, delete categories
3. Visual Indicators - Color-coded categories
4. Enhanced Filtering - Filter by category
5. Category Statistics - Show progress per category
Let me start implementing this feature:
+     1: namespace TaskQuest.Models;
+     2: 
+     3: public class QuestCategory
+     4: {
+     5:     public int Id { get; set; }
+     6:     public string Name { get; set; } = string.Empty;
+     7:     public string Description { get; set; } = string.Empty;
+     8:     public string Color { get; set; } = "#6c757d"; // Default gray color
+     9:     public string Icon { get; set; } = "fas fa-folder";
+    10:     public DateTime CreatedAt { get; set; } = DateTime.Now;
+    11:     public bool IsDefault { get; set; } = false;
+    12: }
+    13: 
+    14: public static class DefaultCategories
+    15: {
+    16:     public static readonly List<QuestCategory> Categories = [
+    17:         new()
+    18:         {
+    19:             Id = 1,
+    20:             Name = "Work",
+    21:             Description = "Professional tasks and projects",
+    22:             Color = "#0d6efd", // Bootstrap primary blue
+    23:             Icon = "fas fa-briefcase",
+    24:             IsDefault = true
+    25:         },
+    26:         new()
+    27:         {
+    28:             Id = 2,
+    29:             Name = "Personal",
+    30:             Description = "Personal life and self-care tasks",
+    31:             Color = "#198754", // Bootstrap success green
+    32:             Icon = "fas fa-user",
+    33:             IsDefault = true
+    34:         },
+    35:         new()
+    36:         {
+    37:             Id = 3,
+    38:             Name = "Learning",
+    39:             Description = "Educational and skill development tasks",
+    40:             Color = "#dc3545", // Bootstrap danger red
+    41:             Icon = "fas fa-graduation-cap",
+    42:             IsDefault = true
+    43:         },
+    44:         new()
+    45:         {
+    46:             Id = 4,
+    47:             Name = "Health",
+    48:             Description = "Health and fitness related tasks",
+    49:             Color = "#fd7e14", // Bootstrap warning orange
+    50:             Icon = "fas fa-heartbeat",
+    51:             IsDefault = true
+    52:         },
+    53:         new()
+    54:         {
+    55:             Id = 5,
+    56:             Name = "Creative",
+    57:             Description = "Creative projects and hobbies",
+    58:             Color = "#6f42c1", // Bootstrap purple
+    59:             Icon = "fas fa-palette",
+    60:             IsDefault = true
+    61:         }
+    62:     ];
+    63: }

I see it is creating a new Model for the QuestCategory, along with populating DefaultCategories. Approved.

Next, it updated the QuestTask model to include a CategoryId and a Category. Approved.

   1,  1: namespace TaskQuest.Models;
   2,  2: 
   3,  3: public class QuestTask
   4,  4: {
   5,  5: 	public int Id { get; set; }
   6,  6: 	public string Title { get; set; } = string.Empty;
   7,  7: 	public string Description { get; set; } = string.Empty;
   8,  8: 	public TaskPriority Priority { get; set; } = TaskPriority.Medium;
   9,  9: 	public bool IsCompleted { get; set; }
  10, 10: 	public DateTime CreatedAt { get; set; } = DateTime.Now;
  11, 11: 	public DateTime? CompletedAt { get; set; }
  12, 12: 	public int ExperiencePoints { get; set; }
  13, 13: 	public string Icon { get; set; } = "fas fa-tasks";
+     14: 	public int CategoryId { get; set; } = 1; // Default to Work category
+     15: 	public QuestCategory? Category { get; set; }
  14, 16: }

Next, it created a CategoryService that is initialized with default categories and a field, List<QuestCatory> _categories, to store the current categories.

+     1: using TaskQuest.Models;
+     2: 
+     3: namespace TaskQuest.Services;
+     4: 
+     5: public class CategoryService
+     6: {
+     7:     readonly List<QuestCategory> _categories = [];
+     8:     int _nextId = 6; // Start after default categories
+     9: 
+    10:     public CategoryService()
+    11:     {
+    12:         // Initialize with default categories
+    13:         _categories.AddRange(DefaultCategories.Categories);
+    14:     }
+    15: 
+    16:     public List<QuestCategory> GetAllCategories()
+    17:     {
+    18:         return _categories;
+    19:     }
+    20: 
+    21:     public QuestCategory? GetCategoryById(int id)
+    22:     {
+    23:         return _categories.FirstOrDefault(c => c.Id == id);
+    24:     }
+    25: 
+    26:     public QuestCategory CreateCategory(QuestCategory category)
+    27:     {
+    28:         category.Id = _nextId++;
+    29:         _categories.Add(category);
+    30:         return category;
+    31:     }
+    32: 
+    33:     public QuestCategory UpdateCategory(QuestCategory category)
+    34:     {
+    35:         var existingCategory = _categories.FirstOrDefault(c => c.Id == category.Id);
+    36:         if (existingCategory != null)
+    37:         {
+    38:             var index = _categories.IndexOf(existingCategory);
+    39:             _categories[index] = category;
+    40:         }
+    41:         return category;
+    42:     }
+    43: 
+    44:     public bool DeleteCategory(int id)
+    45:     {
+    46:         var category = _categories.FirstOrDefault(c => c.Id == id);
+    47:         if (category != null && !category.IsDefault)
+    48:         {
+    49:             _categories.Remove(category);
+    50:             return true;
+    51:         }
+    52:         return false;
+    53:     }
+    54: }

Next, it updated the TaskService to inject the new CategoryService:

    1,   1: using TaskQuest.Models;
    2,   2: 
    3,   3: namespace TaskQuest.Services;
    4,   4: 
    5,   5: public class TaskService : ITaskService
    6,   6: {
    7,   7: 	readonly List<QuestTask> _tasks = [];
+        8: 	readonly CategoryService _categoryService;
    8,   9: 	int _nextId = 1;
    9,  10: 
-  10     : 	public TaskService()
+       11: 	public TaskService(CategoryService categoryService)
   11,  12: 	{
+       13: 		_categoryService = categoryService;
   12,  14: 		// Initialize with some sample tasks
   13,  15: 		SeedData();
   14,  16: 	}
   15,  17: 
   16,  18: 	public Task<List<QuestTask>> GetAllTasksAsync()
   17,  19: 	{
+       20: 		// Populate category information for each task
+       21: 		foreach (var task in _tasks)
+       22: 		{
+       23: 			task.Category = _categoryService.GetCategoryById(task.CategoryId);
+       24: 		}
   18,  25: 		return Task.FromResult(_tasks);
   19,  26: 	}
   20,  27: 
   21,  28: 	public Task<QuestTask?> GetTaskByIdAsync(int id)
   22,  29: 	{
   23,  30: 		var task = _tasks.FirstOrDefault(t => t.Id == id);
+       31: 		if (task != null)
+       32: 		{
+       33: 			task.Category = _categoryService.GetCategoryById(task.CategoryId);
+       34: 		}
   24,  35: 		return Task.FromResult(task);
   25,  36: 	}
   26,  37: 
   27,  38: 	public Task<QuestTask> CreateTaskAsync(QuestTask task)
   28,  39: 	{
   29,  40: 		task.Id = _nextId++;
   30,  41: 		task.ExperiencePoints = task.Priority.GetExperiencePoints();
+       42: 		task.Category = _categoryService.GetCategoryById(task.CategoryId);
   31,  43: 		_tasks.Add(task);
   32,  44: 		return Task.FromResult(task);
   33,  45: 	}
   34,  46: 
   35,  47: 	public Task<QuestTask> UpdateTaskAsync(QuestTask task)
   36,  48: 	{
   37,  49: 		var existingTask = _tasks.FirstOrDefault(t => t.Id == task.Id);
   38,  50: 		if (existingTask != null)
   39,  51: 		{
   40,  52: 			var index = _tasks.IndexOf(existingTask);
   41,  53: 			task.ExperiencePoints = task.Priority.GetExperiencePoints();
+       54: 			task.Category = _categoryService.GetCategoryById(task.CategoryId);
   42,  55: 			_tasks[index] = task;
   43,  56: 		}
   44,  57: 		return Task.FromResult(task);
   45,  58: 	}
   46,  59: 
   47,  60: 	public Task<bool> DeleteTaskAsync(int id)
   48,  61: 	{
   49,  62: 		var task = _tasks.FirstOrDefault(t => t.Id == id);
   50,  63: 		if (task != null)
   51,  64: 		{
   52,  65: 			_tasks.Remove(task);
   53,  66: 			return Task.FromResult(true);
   54,  67: 		}
   55,  68: 		return Task.FromResult(false);
   56,  69: 	}
   57,  70: 
   58,  71: 	public Task<QuestTask> CompleteTaskAsync(int id)
   59,  72: 	{
   60,  73: 		var task = _tasks.FirstOrDefault(t => t.Id == id);
   61,  74: 		if (task != null)
   62,  75: 		{
   63,  76: 			task.IsCompleted = true;
   64,  77: 			task.CompletedAt = DateTime.Now;
   65,  78: 		}
   66,  79: 		return Task.FromResult(task!);
   67,  80: 	}
   68,  81: 
   69,  82: 	public Task<int> GetTotalExperiencePointsAsync()
   70,  83: 	{
   71,  84: 		var totalXp = _tasks.Where(t => t.IsCompleted).Sum(t => t.ExperiencePoints);
   72,  85: 		return Task.FromResult(totalXp);
   73,  86: 	}
   74,  87: 
   75,  88: 	public Task<int> GetCompletedTasksCountAsync()
   76,  89: 	{
   77,  90: 		var completedCount = _tasks.Count(t => t.IsCompleted);
   78,  91: 		return Task.FromResult(completedCount);
   79,  92: 	}
   80,  93: 
+       94: 	public List<QuestTask> GetTasksByCategory(int categoryId)
+       95: 	{
+       96: 		return _tasks.Where(t => t.CategoryId == categoryId).ToList();
+       97: 	}
+       98: 
+       99: 	public int GetCompletedTasksCountByCategory(int categoryId)
+      100: 	{
+      101: 		return _tasks.Count(t => t.CategoryId == categoryId && t.IsCompleted);
+      102: 	}
+      103: 
   81, 104: 	void SeedData()
   82, 105: 	{
   83, 106: 		List<QuestTask> sampleTasks = [
   84, 107: 			new()
   85, 108: 			{
   86, 109: 				Id = _nextId++,
   87, 110: 				Title = "Welcome to Task Quest!",
   88, 111: 				Description = "Complete this task to earn your first experience points",
   89, 112: 				Priority = TaskPriority.Low,
   90, 113: 				Icon = "fas fa-star",
+      114: 				CategoryId = 2, // Personal
   91, 115: 				ExperiencePoints = TaskPriority.Low.GetExperiencePoints()
   92, 116: 			},
   93, 117: 			new()
   94, 118: 			{
   95, 119: 				Id = _nextId++,
   96, 120: 				Title = "Review Project Documentation",
   97, 121: 				Description = "Go through the project requirements and technical specifications",
   98, 122: 				Priority = TaskPriority.Medium,
   99, 123: 				Icon = "fas fa-book",
+      124: 				CategoryId = 1, // Work
  100, 125: 				ExperiencePoints = TaskPriority.Medium.GetExperiencePoints()
  101, 126: 			},
  102, 127: 			new()
  103, 128: 			{
  104, 129: 				Id = _nextId++,
  105, 130: 				Title = "Deploy to Production",
  106, 131: 				Description = "Deploy the latest version to the production environment",
  107, 132: 				Priority = TaskPriority.Critical,
  108, 133: 				Icon = "fas fa-rocket",
+      134: 				CategoryId = 1, // Work
  109, 135: 				ExperiencePoints = TaskPriority.Critical.GetExperiencePoints()
  110, 136: 			}
  111, 137: 		];
  112, 138: 
  113, 139: 		_tasks.AddRange(sampleTasks);
  114, 140: 	}
  115, 141: }

Next, it updated the existing TaskQuestViewModel to support categories:

    1,   1: using CommunityToolkit.Mvvm.ComponentModel;
    2,   2: using CommunityToolkit.Mvvm.Input;
    3,   3: using TaskQuest.Models;
    4,   4: using TaskQuest.Services;
    5,   5: 
    6,   6: namespace TaskQuest.ViewModels;
    7,   7: 
    8,   8: public partial class TaskQuestViewModel : ObservableObject
    9,   9: {
   10,  10: 	readonly ITaskService _taskService;
+       11: 	readonly CategoryService _categoryService;
   11,  12: 
-  12     : 	public TaskQuestViewModel(ITaskService taskService)
+       13: 	public TaskQuestViewModel(ITaskService taskService, CategoryService categoryService)
   13,  14: 	{
   14,  15: 		_taskService = taskService;
+       16: 		_categoryService = categoryService;
   15,  17: 		_ = LoadDataAsync();
   16,  18: 	}
   17,  19: 
   18,  20: 	public List<QuestTask> FilteredTasks
   19,  21: 	{
   20,  22: 		get
   21,  23: 		{
-  22     : 			return FilterStatus switch
+       24: 			var tasks = FilterStatus switch
   23,  25: 			{
-  24     : 				"Completed" => [.. Tasks.Where(t => t.IsCompleted)],
-  25     : 				"Pending" => [.. Tasks.Where(t => !t.IsCompleted)],
-  26     : 				_ => [.. Tasks]
+       26: 				"Completed" => Tasks.Where(t => t.IsCompleted),
+       27: 				"Pending" => Tasks.Where(t => !t.IsCompleted),
+       28: 				_ => Tasks
   27,  29: 			};
+       30: 
+       31: 			if (SelectedCategoryId > 0)
+       32: 			{
+       33: 				tasks = tasks.Where(t => t.CategoryId == SelectedCategoryId);
+       34: 			}
+       35: 
+       36: 			return [.. tasks];
   28,  37: 		}
   29,  38: 	}
   30,  39: 
   31,  40: 	public int PlayerLevel => (TotalExperience / 100) + 1;
   32,  41: 
   33,  42: 	public int ExperienceToNextLevel => 100 - (TotalExperience % 100);
   34,  43: 
   35,  44: 	public double ExperienceProgress => (TotalExperience % 100) / 100.0 * 100;
   36,  45: 
   37,  46: 	public IReadOnlyList<string> FilterOptions => ["All", "Pending", "Completed"];
   38,  47: 
   39,  48: 	public IReadOnlyList<TaskPriority> PriorityOptions => [TaskPriority.Low, TaskPriority.Medium, TaskPriority.High, TaskPriority.Critical];
   40,  49: 
   41,  50: 	public IReadOnlyList<string> IconOptions => [
   42,  51: 		"fas fa-tasks", "fas fa-star", "fas fa-rocket", "fas fa-book",
   43,  52: 		"fas fa-code", "fas fa-bug", "fas fa-lightbulb", "fas fa-cog",
   44,  53: 		"fas fa-chart-line", "fas fa-users", "fas fa-envelope", "fas fa-phone"
   45,  54: 	];
   46,  55: 
   47,  56: 	[ObservableProperty]
   48,  57: 	[NotifyPropertyChangedFor(nameof(FilteredTasks))]
   49,  58: 	public partial List<QuestTask> Tasks { get; set; } = [];
   50,  59: 
   51,  60: 	[ObservableProperty]
   52,  61: 	public partial QuestTask CurrentTask { get; set; } = new();
   53,  62: 
   54,  63: 	[ObservableProperty]
   55,  64: 	public partial bool IsEditing { get; set; }
   56,  65: 
   57,  66: 	[ObservableProperty]
   58,  67: 	[NotifyPropertyChangedFor(nameof(PlayerLevel), nameof(ExperienceToNextLevel), nameof(ExperienceProgress))]
   59,  68: 	public partial int TotalExperience { get; set; }
   60,  69: 
   61,  70: 	[ObservableProperty]
   62,  71: 	public partial int CompletedTasksCount { get; set; }
   63,  72: 
   64,  73: 	[ObservableProperty]
   65,  74: 	[NotifyPropertyChangedFor(nameof(FilteredTasks))]
   66,  75: 	public partial string FilterStatus { get; set; } = "All";
   67,  76: 
+       77: 	[ObservableProperty]
+       78: 	public partial List<QuestCategory> Categories { get; set; } = [];
+       79: 
+       80: 	[ObservableProperty]
+       81: 	[NotifyPropertyChangedFor(nameof(FilteredTasks))]
+       82: 	public partial int SelectedCategoryId { get; set; } = 0; // 0 means all categories
+       83: 
+       84: 	[ObservableProperty]
+       85: 	public partial QuestCategory CurrentCategory { get; set; } = new();
+       86: 
+       87: 	[ObservableProperty]
+       88: 	public partial bool IsEditingCategory { get; set; }
+       89: 
+       90: 	[ObservableProperty]
+       91: 	public partial bool ShowCategoryModal { get; set; }
+       92: 
   68,  93: 	[RelayCommand]
   69,  94: 	public async Task LoadDataAsync()
   70,  95: 	{
   71,  96: 		Tasks = await _taskService.GetAllTasksAsync();
+       97: 		Categories = _categoryService.GetAllCategories();
   72,  98: 		await UpdateStatsAsync();
   73,  99: 	}
   74, 100: 
   75, 101: 	[RelayCommand]
   76, 102: 	public async Task CreateTaskAsync()
   77, 103: 	{
   78, 104: 		if (string.IsNullOrWhiteSpace(CurrentTask.Title)) return;
   79, 105: 
   80, 106: 		await _taskService.CreateTaskAsync(CurrentTask);
   81, 107: 		await LoadDataAsync();
   82, 108: 		ResetCurrentTask();
   83, 109: 	}
   84, 110: 
   85, 111: 	[RelayCommand]
   86, 112: 	public async Task UpdateTaskAsync()
   87, 113: 	{
   88, 114: 		await _taskService.UpdateTaskAsync(CurrentTask);
   89, 115: 		await LoadDataAsync();
   90, 116: 		ResetCurrentTask();
   91, 117: 		IsEditing = false;
   92, 118: 	}
   93, 119: 
   94, 120: 	[RelayCommand]
   95, 121: 	public async Task DeleteTaskAsync(int id)
   96, 122: 	{
   97, 123: 		await _taskService.DeleteTaskAsync(id);
   98, 124: 		await LoadDataAsync();
   99, 125: 	}
  100, 126: 
  101, 127: 	[RelayCommand]
  102, 128: 	public async Task CompleteTaskAsync(int id)
  103, 129: 	{
  104, 130: 		await _taskService.CompleteTaskAsync(id);
  105, 131: 		await LoadDataAsync();
  106, 132: 	}
  107, 133: 
  108, 134: 	[RelayCommand]
  109, 135: 	public void EditTask(QuestTask task)
  110, 136: 	{
  111, 137: 		CurrentTask = new QuestTask
  112, 138: 		{
  113, 139: 			Id = task.Id,
  114, 140: 			Title = task.Title,
  115, 141: 			Description = task.Description,
  116, 142: 			Priority = task.Priority,
  117, 143: 			Icon = task.Icon,
+      144: 			CategoryId = task.CategoryId,
  118, 145: 			IsCompleted = task.IsCompleted,
  119, 146: 			CreatedAt = task.CreatedAt,
  120, 147: 			CompletedAt = task.CompletedAt,
  121, 148: 			ExperiencePoints = task.ExperiencePoints
  122, 149: 		};
  123, 150: 		IsEditing = true;
  124, 151: 	}
  125, 152: 
  126, 153: 	[RelayCommand]
  127, 154: 	public void ResetCurrentTask()
  128, 155: 	{
- 129     : 		CurrentTask = new QuestTask();
+      156: 		CurrentTask = new QuestTask { CategoryId = Categories.FirstOrDefault()?.Id ?? 1 };
  130, 157: 		IsEditing = false;
  131, 158: 	}
  132, 159: 
+      160: 	[RelayCommand]
+      161: 	public void ShowCreateCategoryModal()
+      162: 	{
+      163: 		CurrentCategory = new QuestCategory();
+      164: 		IsEditingCategory = false;
+      165: 		ShowCategoryModal = true;
+      166: 	}
+      167: 
+      168: 	[RelayCommand]
+      169: 	public void ShowEditCategoryModal(QuestCategory category)
+      170: 	{
+      171: 		CurrentCategory = new QuestCategory
+      172: 		{
+      173: 			Id = category.Id,
+      174: 			Name = category.Name,
+      175: 			Description = category.Description,
+      176: 			Color = category.Color,
+      177: 			Icon = category.Icon,
+      178: 			IsDefault = category.IsDefault
+      179: 		};
+      180: 		IsEditingCategory = true;
+      181: 		ShowCategoryModal = true;
+      182: 	}
+      183: 
+      184: 	[RelayCommand]
+      185: 	public async Task SaveCategoryAsync()
+      186: 	{
+      187: 		if (string.IsNullOrWhiteSpace(CurrentCategory.Name)) return;
+      188: 
+      189: 		if (IsEditingCategory)
+      190: 		{
+      191: 			_categoryService.UpdateCategory(CurrentCategory);
+      192: 		}
+      193: 		else
+      194: 		{
+      195: 			_categoryService.CreateCategory(CurrentCategory);
+      196: 		}
+      197: 
+      198: 		await LoadDataAsync();
+      199: 		ShowCategoryModal = false;
+      200: 		ResetCurrentCategory();
+      201: 	}
+      202: 
+      203: 	[RelayCommand]
+      204: 	public async Task DeleteCategoryAsync(int id)
+      205: 	{
+      206: 		_categoryService.DeleteCategory(id);
+      207: 		await LoadDataAsync();
+      208: 	}
+      209: 
+      210: 	[RelayCommand]
+      211: 	public void ResetCurrentCategory()
+      212: 	{
+      213: 		CurrentCategory = new QuestCategory();
+      214: 		IsEditingCategory = false;
+      215: 		ShowCategoryModal = false;
+      216: 	}
+      217: 
  133, 218: 	async Task UpdateStatsAsync()
  134, 219: 	{
  135, 220: 		TotalExperience = await _taskService.GetTotalExperiencePointsAsync();
  136, 221: 		CompletedTasksCount = await _taskService.GetCompletedTasksCountAsync();
  137, 222: 	}
  138, 223: }

Next, it updated Program.cs to register CategoryService:

   1,  1: using TaskQuest.Components;
   2,  2: using TaskQuest.Services;
   3,  3: using TaskQuest.ViewModels;
   4,  4: 
   5,  5: var builder = WebApplication.CreateBuilder(args);
   6,  6: 
   7,  7: // Add services to the container.
   8,  8: builder.Services.AddRazorComponents()
   9,  9: 	.AddInteractiveServerComponents();
  10, 10: 
  11, 11: // Register MVVM services
+     12: builder.Services.AddScoped<CategoryService>();
  12, 13: builder.Services.AddScoped<ITaskService, TaskService>();
  13, 14: builder.Services.AddScoped<TaskQuestViewModel>();
  14, 15: 
  15, 16: var app = builder.Build();
  16, 17: 
  17, 18: // Configure the HTTP request pipeline.
  18, 19: if (!app.Environment.IsDevelopment())
  19, 20: {
  20, 21: 	app.UseExceptionHandler("/Error", createScopeForErrors: true);
  21, 22: 	app.UseHsts();
  22, 23: }
  23, 24: 
  24, 25: app.UseHttpsRedirection();
  25, 26: app.UseStaticFiles();
  26, 27: app.UseAntiforgery();
  27, 28: 
  28, 29: app.MapRazorComponents<App>()
  29, 30: 	.AddInteractiveServerRenderMode();
  30, 31: 
  31, 32: app.Run();

Next, it created a new component for the CategoryModel

+     1: @using TaskQuest.ViewModels
+     2: @using TaskQuest.Models
+     3: 
+     4: @if (ViewModel.ShowCategoryModal)
+     5: {
+     6:     <div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
+     7:         <div class="modal-dialog modal-dialog-centered">
+     8:             <div class="modal-content">
+     9:                 <div class="modal-header">
+    10:                     <h5 class="modal-title">
+    11:                         <i class="fas @(ViewModel.IsEditingCategory ? "fa-edit" : "fa-plus") me-2"></i>
+    12:                         @(ViewModel.IsEditingCategory ? "Edit Category" : "Create New Category")
+    13:                     </h5>
+    14:                     <button type="button" class="btn-close" @onclick="ViewModel.ResetCurrentCategoryCommand.Execute"></button>
+    15:                 </div>
+    16:                 <div class="modal-body">
+    17:                     <EditForm Model="ViewModel.CurrentCategory" OnValidSubmit="ViewModel.SaveCategoryCommand.ExecuteAsync">
+    18:                         <div class="mb-3">
+    19:                             <label class="form-label">Category Name</label>
+    20:                             <InputText @bind-Value="ViewModel.CurrentCategory.Name" class="form-control" placeholder="Enter category name..." />
+    21:                         </div>
+    22:                         
+    23:                         <div class="mb-3">
+    24:                             <label class="form-label">Description</label>
+    25:                             <InputTextArea @bind-Value="ViewModel.CurrentCategory.Description" class="form-control" rows="2" placeholder="Describe this category..." />
+    26:                         </div>
+    27:                         
+    28:                         <div class="row mb-3">
+    29:                             <div class="col-6">
+    30:                                 <label class="form-label">Color</label>
+    31:                                 <InputText @bind-Value="ViewModel.CurrentCategory.Color" type="color" class="form-control form-control-color" />
+    32:                             </div>
+    33:                             <div class="col-6">
+    34:                                 <label class="form-label">Icon</label>
+    35:                                 <InputSelect @bind-Value="ViewModel.CurrentCategory.Icon" class="form-select">
+    36:                                     <option value="fas fa-folder">Folder</option>
+    37:                                     <option value="fas fa-briefcase">Briefcase</option>
+    38:                                     <option value="fas fa-user">User</option>
+    39:                                     <option value="fas fa-graduation-cap">Graduation Cap</option>
+    40:                                     <option value="fas fa-heartbeat">Heartbeat</option>
+    41:                                     <option value="fas fa-palette">Palette</option>
+    42:                                     <option value="fas fa-home">Home</option>
+    43:                                     <option value="fas fa-shopping-cart">Shopping Cart</option>
+    44:                                     <option value="fas fa-car">Car</option>
+    45:                                     <option value="fas fa-plane">Plane</option>
+    46:                                 </InputSelect>
+    47:                             </div>
+    48:                         </div>
+    49:                         
+    50:                         <div class="category-preview mb-3">
+    51:                             <label class="form-label">Preview</label>
+    52:                             <div class="d-flex align-items-center">
+    53:                                 <span class="category-badge me-2" style="background-color: @ViewModel.CurrentCategory.Color;">
+    54:                                     <i class="@ViewModel.CurrentCategory.Icon text-white"></i>
+    55:                                 </span>
+    56:                                 <span>@(string.IsNullOrEmpty(ViewModel.CurrentCategory.Name) ? "Category Name" : ViewModel.CurrentCategory.Name)</span>
+    57:                             </div>
+    58:                         </div>
+    59:                         
+    60:                         <div class="d-flex justify-content-end gap-2">
+    61:                             <button type="button" class="btn btn-secondary" @onclick="ViewModel.ResetCurrentCategoryCommand.Execute">
+    62:                                 Cancel
+    63:                             </button>
+    64:                             <button type="submit" class="btn btn-primary">
+    65:                                 <i class="fas @(ViewModel.IsEditingCategory ? "fa-save" : "fa-plus") me-2"></i>
+    66:                                 @(ViewModel.IsEditingCategory ? "Update" : "Create")
+    67:                             </button>
+    68:                         </div>
+    69:                     </EditForm>
+    70:                 </div>
+    71:             </div>
+    72:         </div>
+    73:     </div>
+    74: }
+    75: 
+    76: <style>
+    77:     .category-badge {
+    78:         display: inline-flex;
+    79:         align-items: center;
+    80:         justify-content: center;
+    81:         width: 32px;
+    82:         height: 32px;
+    83:         border-radius: 50%;
+    84:         font-size: 14px;
+    85:     }
+    86: </style>
+    87: 
+    88: @code {
+    89:     [Parameter] public TaskQuestViewModel ViewModel { get; set; } = default!;
+    90: }

Next, it updated Home.razor to include the new category feature:

    1,   1: @page "/"
    2,   2: @using TaskQuest.ViewModels
    3,   3: @using TaskQuest.Models
    4,   4: @inject TaskQuestViewModel ViewModel
    5,   5: @rendermode InteractiveServer
    6,   6: 
    7,   7: <PageTitle>Task Quest - Gamify Your Productivity</PageTitle>
    8,   8: 
    9,   9: <div class="container-fluid task-quest-app">
   10,  10:     <!-- Header Section -->
   11,  11:     <div class="row mb-4">
   12,  12:         <div class="col-12">
   13,  13:             <div class="hero-section text-center py-4">
   14,  14:                 <h1 class="display-4 text-gradient">
   15,  15:                     <i class="fas fa-dragon me-3"></i>Task Quest
   16,  16:                 </h1>
   17,  17:                 <p class="lead">Transform your productivity into an epic adventure!</p>
   18,  18:             </div>
   19,  19:         </div>
   20,  20:     </div>
   21,  21: 
   22,  22:     <!-- Stats Dashboard -->
   23,  23:     <div class="row mb-4">
   24,  24:         <div class="col-md-3 col-sm-6 mb-3">
   25,  25:             <div class="stat-card level-card">
   26,  26:                 <div class="stat-icon">
   27,  27:                     <i class="fas fa-crown"></i>
   28,  28:                 </div>
   29,  29:                 <div class="stat-content">
   30,  30:                     <h3>Level @ViewModel.PlayerLevel</h3>
   31,  31:                     <p>Productivity Hero</p>
   32,  32:                 </div>
   33,  33:             </div>
   34,  34:         </div>
   35,  35:         <div class="col-md-3 col-sm-6 mb-3">
   36,  36:             <div class="stat-card xp-card">
   37,  37:                 <div class="stat-icon">
   38,  38:                     <i class="fas fa-star"></i>
   39,  39:                 </div>
   40,  40:                 <div class="stat-content">
   41,  41:                     <h3>@ViewModel.TotalExperience XP</h3>
   42,  42:                     <p>Total Experience</p>
   43,  43:                     <div class="progress mt-2">
   44,  44:                         <div class="progress-bar progress-bar-animated" 
   45,  45:                              style="width: @(ViewModel.ExperienceProgress)%"></div>
   46,  46:                     </div>
   47,  47:                     <small>@ViewModel.ExperienceToNextLevel XP to next level</small>
   48,  48:                 </div>
   49,  49:             </div>
   50,  50:         </div>
   51,  51:         <div class="col-md-3 col-sm-6 mb-3">
   52,  52:             <div class="stat-card completed-card">
   53,  53:                 <div class="stat-icon">
   54,  54:                     <i class="fas fa-trophy"></i>
   55,  55:                 </div>
   56,  56:                 <div class="stat-content">
   57,  57:                     <h3>@ViewModel.CompletedTasksCount</h3>
   58,  58:                     <p>Quests Completed</p>
   59,  59:                 </div>
   60,  60:             </div>
   61,  61:         </div>
   62,  62:         <div class="col-md-3 col-sm-6 mb-3">
   63,  63:             <div class="stat-card pending-card">
   64,  64:                 <div class="stat-icon">
   65,  65:                     <i class="fas fa-hourglass-half"></i>
   66,  66:                 </div>
   67,  67:                 <div class="stat-content">
   68,  68:                     <h3>@(ViewModel.Tasks.Count - ViewModel.CompletedTasksCount)</h3>
   69,  69:                     <p>Active Quests</p>
   70,  70:                 </div>
   71,  71:             </div>
   72,  72:         </div>
   73,  73:     </div>
   74,  74: 
+       75:     <!-- Categories Section -->
+       76:     <div class="row mb-4">
+       77:         <div class="col-12">
+       78:             <div class="card category-card">
+       79:                 <div class="card-header d-flex justify-content-between align-items-center">
+       80:                     <h5 class="mb-0">
+       81:                         <i class="fas fa-tags me-2"></i>Quest Categories
+       82:                     </h5>
+       83:                     <button class="btn btn-primary btn-sm" @onclick="ViewModel.ShowCreateCategoryModalCommand.Execute">
+       84:                         <i class="fas fa-plus me-1"></i>Add Category
+       85:                     </button>
+       86:                 </div>
+       87:                 <div class="card-body">
+       88:                     <div class="row">
+       89:                         <div class="col-auto mb-2">
+       90:                             <button class="btn @(ViewModel.SelectedCategoryId == 0 ? "btn-primary" : "btn-outline-secondary") btn-sm"
+       91:                                     @onclick="() => ViewModel.SelectedCategoryId = 0">
+       92:                                 <i class="fas fa-list me-1"></i>All Categories
+       93:                             </button>
+       94:                         </div>
+       95:                         @foreach (var category in ViewModel.Categories)
+       96:                         {
+       97:                             <div class="col-auto mb-2">
+       98:                                 <div class="category-item @(ViewModel.SelectedCategoryId == category.Id ? "active" : "")"
+       99:                                      @onclick="() => ViewModel.SelectedCategoryId = category.Id">
+      100:                                     <span class="category-badge me-2" style="background-color: @category.Color;">
+      101:                                         <i class="@category.Icon text-white"></i>
+      102:                                     </span>
+      103:                                     <span class="category-name">@category.Name</span>
+      104:                                     @if (!category.IsDefault)
+      105:                                     {
+      106:                                         <div class="category-actions ms-2">
+      107:                                             <button class="btn btn-sm btn-outline-warning me-1" 
+      108:                                                     @onclick:stopPropagation="true"
+      109:                                                     @onclick="() => ViewModel.ShowEditCategoryModalCommand.Execute(category)">
+      110:                                                 <i class="fas fa-edit"></i>
+      111:                                             </button>
+      112:                                             <button class="btn btn-sm btn-outline-danger" 
+      113:                                                     @onclick:stopPropagation="true"
+      114:                                                     @onclick="() => ViewModel.DeleteCategoryCommand.ExecuteAsync(category.Id)">
+      115:                                                 <i class="fas fa-trash"></i>
+      116:                                             </button>
+      117:                                         </div>
+      118:                                     }
+      119:                                 </div>
+      120:                             </div>
+      121:                         }
+      122:                     </div>
+      123:                 </div>
+      124:             </div>
+      125:         </div>
+      126:     </div>
+      127: 
   75, 128:     <!-- Task Management Section -->
   76, 129:     <div class="row">
   77, 130:         <div class="col-lg-4 mb-4">
   78, 131:             <div class="card task-form-card">
   79, 132:                 <div class="card-header">
   80, 133:                     <h5 class="mb-0">
   81, 134:                         <i class="fas @(ViewModel.IsEditing ? "fa-edit" : "fa-plus") me-2"></i>
   82, 135:                         @(ViewModel.IsEditing ? "Edit Quest" : "Create New Quest")
   83, 136:                     </h5>
   84, 137:                 </div>
   85, 138:                 <div class="card-body">
   86, 139:                     <EditForm Model="ViewModel.CurrentTask" OnValidSubmit="@(ViewModel.IsEditing ? ViewModel.UpdateTaskCommand.ExecuteAsync : ViewModel.CreateTaskCommand.ExecuteAsync)">
   87, 140:                         <div class="mb-3">
   88, 141:                             <label class="form-label">Quest Title</label>
   89, 142:                             <InputText @bind-Value="ViewModel.CurrentTask.Title" class="form-control" placeholder="Enter quest title..." />
   90, 143:                         </div>
   91, 144:                         
   92, 145:                         <div class="mb-3">
   93, 146:                             <label class="form-label">Description</label>
   94, 147:                             <InputTextArea @bind-Value="ViewModel.CurrentTask.Description" class="form-control" rows="3" placeholder="Describe your quest..." />
   95, 148:                         </div>
   96, 149:                         
+      150:                         <div class="mb-3">
+      151:                             <label class="form-label">Category</label>
+      152:                             <InputSelect @bind-Value="ViewModel.CurrentTask.CategoryId" class="form-select">
+      153:                                 @foreach (var category in ViewModel.Categories)
+      154:                                 {
+      155:                                     <option value="@category.Id">@category.Name</option>
+      156:                                 }
+      157:                             </InputSelect>
+      158:                         </div>
+      159:                         
   97, 160:                         <div class="row mb-3">
   98, 161:                             <div class="col-6">
   99, 162:                                 <label class="form-label">Priority</label>
  100, 163:                                 <InputSelect @bind-Value="ViewModel.CurrentTask.Priority" class="form-select">
  101, 164:                                     @foreach (var priority in ViewModel.PriorityOptions)
  102, 165:                                     {
  103, 166:                                         <option value="@priority">@priority.GetDisplayName()</option>
  104, 167:                                     }
  105, 168:                                 </InputSelect>
  106, 169:                             </div>
  107, 170:                             <div class="col-6">
  108, 171:                                 <label class="form-label">Icon</label>
  109, 172:                                 <InputSelect @bind-Value="ViewModel.CurrentTask.Icon" class="form-select">
  110, 173:                                     @foreach (var icon in ViewModel.IconOptions)
  111, 174:                                     {
  112, 175:                                         <option value="@icon">@icon.Replace("fas fa-", "").Replace("-", " ")</option>
  113, 176:                                     }
  114, 177:                                 </InputSelect>
  115, 178:                             </div>
  116, 179:                         </div>
  117, 180:                         
  118, 181:                         <div class="d-grid gap-2">
  119, 182:                             <button type="submit" class="btn btn-primary">
  120, 183:                                 <i class="fas @(ViewModel.IsEditing ? "fa-save" : "fa-plus") me-2"></i>
  121, 184:                                 @(ViewModel.IsEditing ? "Update Quest" : "Create Quest")
  122, 185:                             </button>
  123, 186:                             @if (ViewModel.IsEditing)
  124, 187:                             {
  125, 188:                                 <button type="button" class="btn btn-secondary" @onclick="ViewModel.ResetCurrentTaskCommand.Execute">
  126, 189:                                     <i class="fas fa-times me-2"></i>Cancel
  127, 190:                                 </button>
  128, 191:                             }
  129, 192:                         </div>
  130, 193:                     </EditForm>
  131, 194:                 </div>
  132, 195:             </div>
  133, 196:         </div>
  134, 197:         
  135, 198:         <div class="col-lg-8">
  136, 199:             <div class="card task-list-card">
  137, 200:                 <div class="card-header d-flex justify-content-between align-items-center">
  138, 201:                     <h5 class="mb-0">
  139, 202:                         <i class="fas fa-list me-2"></i>Your Quests
+      203:                         @if (ViewModel.SelectedCategoryId > 0)
+      204:                         {
+      205:                             var selectedCategory = ViewModel.Categories.FirstOrDefault(c => c.Id == ViewModel.SelectedCategoryId);
+      206:                             if (selectedCategory != null)
+      207:                             {
+      208:                                 <span class="badge ms-2" style="background-color: @selectedCategory.Color;">
+      209:                                     <i class="@selectedCategory.Icon me-1"></i>@selectedCategory.Name
+      210:                                 </span>
+      211:                             }
+      212:                         }
  140, 213:                     </h5>
  141, 214:                     <div class="filter-controls">
  142, 215:                         <select @bind="ViewModel.FilterStatus" class="form-select form-select-sm">
  143, 216:                             @foreach (var filter in ViewModel.FilterOptions)
  144, 217:                             {
  145, 218:                                 <option value="@filter">@filter</option>
  146, 219:                             }
  147, 220:                         </select>
  148, 221:                     </div>
  149, 222:                 </div>
  150, 223:                 <div class="card-body p-0">
  151, 224:                     @if (ViewModel.FilteredTasks.Any())
  152, 225:                     {
  153, 226:                         <div class="task-list">
  154, 227:                             @foreach (var task in ViewModel.FilteredTasks.OrderByDescending(t => t.CreatedAt))
  155, 228:                             {
  156, 229:                                 <div class="task-item @(task.IsCompleted ? "completed" : "") @task.Priority.GetCssClass()">
  157, 230:                                     <div class="task-content">
  158, 231:                                         <div class="task-header">
  159, 232:                                             <div class="task-icon">
  160, 233:                                                 <i class="@task.Icon"></i>
  161, 234:                                             </div>
  162, 235:                                             <div class="task-info">
  163, 236:                                                 <h6 class="task-title @(task.IsCompleted ? "text-decoration-line-through" : "")">
  164, 237:                                                     @task.Title
  165, 238:                                                 </h6>
  166, 239:                                                 <p class="task-description">@task.Description</p>
  167, 240:                                                 <div class="task-meta">
+      241:                                                     @if (task.Category != null)
+      242:                                                     {
+      243:                                                         <span class="badge me-2" style="background-color: @task.Category.Color;">
+      244:                                                             <i class="@task.Category.Icon me-1"></i>@task.Category.Name
+      245:                                                         </span>
+      246:                                                     }
  168, 247:                                                     <span class="badge bg-secondary me-2">@task.Priority.GetDisplayName()</span>
  169, 248:                                                     <span class="badge bg-info me-2">@task.ExperiencePoints XP</span>
  170, 249:                                                     <small class="text-muted">Created: @task.CreatedAt.ToString("MMM dd, yyyy")</small>
  171, 250:                                                     @if (task.IsCompleted && task.CompletedAt.HasValue)
  172, 251:                                                     {
  173, 252:                                                         <small class="text-success ms-2">Completed: @task.CompletedAt.Value.ToString("MMM dd, yyyy")</small>
  174, 253:                                                     }
  175, 254:                                                 </div>
  176, 255:                                             </div>
  177, 256:                                         </div>
  178, 257:                                     </div>
  179, 258:                                     <div class="task-actions">
  180, 259:                                         @if (!task.IsCompleted)
  181, 260:                                         {
  182, 261:                                             <button class="btn btn-success btn-sm me-2" @onclick="() => ViewModel.CompleteTaskCommand.ExecuteAsync(task.Id)">
  183, 262:                                                 <i class="fas fa-check"></i>
  184, 263:                                             </button>
  185, 264:                                             <button class="btn btn-warning btn-sm me-2" @onclick="() => ViewModel.EditTaskCommand.Execute(task)">
  186, 265:                                                 <i class="fas fa-edit"></i>
  187, 266:                                             </button>
  188, 267:                                         }
  189, 268:                                         <button class="btn btn-danger btn-sm" @onclick="() => ViewModel.DeleteTaskCommand.ExecuteAsync(task.Id)">
  190, 269:                                             <i class="fas fa-trash"></i>
  191, 270:                                         </button>
  192, 271:                                     </div>
  193, 272:                                 </div>
  194, 273:                             }
  195, 274:                         </div>
  196, 275:                     }
  197, 276:                     else
  198, 277:                     {
  199, 278:                         <div class="empty-state text-center py-5">
  200, 279:                             <i class="fas fa-dragon fa-3x text-muted mb-3"></i>
  201, 280:                             <h5>No quests found</h5>
  202, 281:                             <p class="text-muted">
- 203     :                                 @if (ViewModel.FilterStatus == "All")
+      282:                                 @if (ViewModel.FilterStatus == "All" && ViewModel.SelectedCategoryId == 0)
  204, 283:                                 {
  205, 284:                                     <text>Create your first quest to begin your adventure!</text>
  206, 285:                                 }
  207, 286:                                 else
  208, 287:                                 {
- 209     :                                     <text>No @ViewModel.FilterStatus.ToLower() quests available.</text>
+      288:                                     <text>No @ViewModel.FilterStatus.ToLower() quests available in this category.</text>
  210, 289:                                 }
  211, 290:                             </p>
  212, 291:                         </div>
  213, 292:                     }
  214, 293:                 </div>
  215, 294:             </div>
  216, 295:         </div>
  217, 296:     </div>
- 218     : </div>
+      297: </div>
+      298: 
+      299: <!-- Category Modal -->

Finally, it updated app.css adding the new styles

   1,   1: /* Task Quest - Professional Gaming-Inspired Styles */
   2,   2: 
   3,   3: :root {
   4,   4:     --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
   5,   5:     --success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
   6,   6:     --warning-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
   7,   7:     --danger-gradient: linear-gradient(135deg, #fc466b 0%, #3f5efb 100%);
   8,   8:     --info-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
   9,   9:     --dark-bg: #1a1a2e;
  10,  10:     --card-bg: #16213e;
  11,  11:     --text-light: #eee;
  12,  12:     --border-color: #0f3460;
  13,  13:     --shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  14,  14:     --glow: 0 0 20px rgba(102, 126, 234, 0.3);
  15,  15: }
  16,  16: 
  17,  17: body {
  18,  18:     background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%);
  19,  19:     color: var(--text-light);
  20,  20:     font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  21,  21:     min-height: 100vh;
  22,  22: }
  23,  23: 
  24,  24: .task-quest-app {
  25,  25:     padding: 20px;
  26,  26:     max-width: 1400px;
  27,  27:     margin: 0 auto;
  28,  28: }
  29,  29: 
  30,  30: /* Hero Section */
  31,  31: .hero-section {
  32,  32:     background: var(--primary-gradient);
  33,  33:     border-radius: 20px;
  34,  34:     box-shadow: var(--shadow);
  35,  35:     margin-bottom: 30px;
  36,  36:     position: relative;
  37,  37:     overflow: hidden;
  38,  38: }
  39,  39: 
  40,  40: .hero-section::before {
  41,  41:     content: '';
  42,  42:     position: absolute;
  43,  43:     top: 0;
  44,  44:     left: 0;
  45,  45:     right: 0;
  46,  46:     bottom: 0;
  47,  47:     background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
  48,  48:     opacity: 0.3;
+      49: }
+      50: 
+      51: /* Category Styles */
+      52: .category-card {
+      53:     background: var(--card-bg);
+      54:     border: 1px solid var(--border-color);
+      55:     border-radius: 15px;
+      56:     box-shadow: var(--shadow);
+      57:     transition: all 0.3s ease;
+      58: }
+      59: 
+      60: .category-card:hover {
+      61:     box-shadow: var(--glow);
+      62:     transform: translateY(-2px);
+      63: }
+      64: 
+      65: .category-item {
+      66:     display: flex;
+      67:     align-items: center;
+      68:     padding: 12px 16px;
+      69:     border-radius: 25px;
+      70:     background: rgba(255, 255, 255, 0.05);
+      71:     border: 2px solid transparent;
+      72:     cursor: pointer;
+      73:     transition: all 0.3s ease;
+      74:     margin-right: 10px;
+      75:     margin-bottom: 8px;
+      76: }
+      77: 
+      78: .category-item:hover {
+      79:     background: rgba(255, 255, 255, 0.1);
+      80:     border-color: rgba(102, 126, 234, 0.5);
+      81:     transform: translateY(-2px);
+      82: }
+      83: 
+      84: .category-item.active {
+      85:     background: var(--primary-gradient);
+      86:     border-color: #667eea;
+      87:     box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
+      88: }
+      89: 
+      90: .category-badge {
+      91:     display: inline-flex;
+      92:     align-items: center;
+      93:     justify-content: center;
+      94:     width: 32px;
+      95:     height: 32px;
+      96:     border-radius: 50%;
+      97:     font-size: 14px;
+      98:     box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+      99: }
+     100: 
+     101: .category-name {
+     102:     font-weight: 600;
+     103:     color: var(--text-light);
+     104: }
+     105: 
+     106: .category-actions {
+     107:     display: flex;
+     108:     align-items: center;
+     109:     opacity: 0;
+     110:     transition: opacity 0.3s ease;
+     111: }
+     112: 
+     113: .category-item:hover .category-actions {
+     114:     opacity: 1;
+     115: }
+     116: 
+     117: /* Modal Styles */
+     118: .modal-content {
+     119:     background: var(--card-bg);
+     120:     border: 1px solid var(--border-color);
+     121:     border-radius: 15px;
+     122:     box-shadow: var(--shadow);
+     123: }
+     124: 
+     125: .modal-header {
+     126:     border-bottom: 1px solid var(--border-color);
+     127:     background: var(--primary-gradient);
+     128:     border-radius: 15px 15px 0 0;
+     129: }
+     130: 
+     131: .modal-title {
+     132:     color: white;
+     133:     font-weight: 600;
+     134: }
+     135: 
+     136: .btn-close {
+     137:     filter: invert(1);
+     138: }
+     139: 
+     140: .form-control, .form-select {
+     141:     background: rgba(255, 255, 255, 0.1);
+     142:     border: 1px solid var(--border-color);
+     143:     color: var(--text-light);
+     144:     border-radius: 8px;
+     145: }
+     146: 
+     147: .form-control:focus, .form-select:focus {
+     148:     background: rgba(255, 255, 255, 0.15);
+     149:     border-color: #667eea;
+     150:     box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
+     151:     color: var(--text-light);
+     152: }
+     153: 
+     154: .form-control::placeholder {
+     155:     color: rgba(238, 238, 238, 0.6);
+     156: }
+     157: 
+     158: .form-label {
+     159:     color: var(--text-light);
+     160:     font-weight: 500;
+     161:     margin-bottom: 8px;
+     162: }
+     163: 
+     164: /* Enhanced Task Item Styles */
+     165: .task-item .task-meta .badge {
+     166:     font-size: 0.75rem;
+     167:     padding: 4px 8px;
+     168:     border-radius: 12px;
+     169:     font-weight: 500;
  49, 170: }