@page "/deck-builder" @page "/deck-builder/{DeckId:guid}" @using Model @using Web.Services @using Shared.Components @inject UserDecksService UserDecksService @inject AuthService AuthService @inject NavigationManager Navigation @inject NetworkStatusService NetworkStatus @implements IDisposable Chrono CCG - @(string.IsNullOrEmpty(deck.Name) ? "Deck Builder" : deck.Name)
@if (!AuthService.IsLoggedIn) {

Authentication Required

Log in to create, customize, and save your Chrono CCG decks to your cloud profile.

Log In to Continue
} else {
40 ? "count-overflow" : "count-progress")" title="Main deck must have exactly 40 cards"> @deck.Cards.Count / 40
@(deck.IsValid ? "Valid" : deck.ValidationMessage)
My Decks
Strategy Notes
Last updated: @deck.UpdatedAt.ToLocalTime().ToString("MMM dd, yyyy HH:mm")
@if (!string.IsNullOrEmpty(errorMessage)) { }
Card Browser
Showing @FilteredBrowserCards.Count() of @allCards.Count(c => (c.Category is "Agent" or "Spell") && !c.IsImmortalized) cards Click card to add (max 3)
@if (!string.IsNullOrEmpty(searchFilter)) { }
@if (!string.IsNullOrEmpty(searchFilter) || !string.IsNullOrEmpty(categoryFilter) || !string.IsNullOrEmpty(syndicateFilter) || !string.IsNullOrEmpty(costFilter)) { }
@foreach (var card in FilteredBrowserCards) { var countInDeck = deck.Cards.Count(c => c == card.Name); var isDiver = deck.Divers.Contains(card.Name); var isMaxed = countInDeck >= 3 || deck.Cards.Count >= 40 || isDiver; var catClass = card.IsAgent ? "card-agent" : card.IsSpell ? "card-spell" : card.IsImmortalized ? "card-immortalized" : "";
@card.Name @if (card.Cost.HasValue) {
@card.Cost
} @if (countInDeck > 0) {
@(countInDeck == 3 ? "3 MAX" : $"{countInDeck}/3")
} @if (isDiver) {
DIVER
}
@card.Name
@card.Category @if (card.Attack.HasValue && card.Health.HasValue) { @card.Attack / @card.Health } else if (!string.IsNullOrEmpty(card.Syndicate)) { @card.Syndicate }
}
Diver Cards (2 Required)
@deck.Divers.Count / 2
@if (deck.Divers.Count > 0 && !string.IsNullOrEmpty(deck.Divers[0])) {
Diver 1
@deck.Divers[0]
} else {
Select Diver 1
}
@if (deck.Divers.Count > 1 && !string.IsNullOrEmpty(deck.Divers[1])) {
Diver 2
@deck.Divers[1]
} else {
Select Diver 2
}
@if (deck.Cards.Any()) {
@AgentsCount @SpellsCount @ImmortalizedCount
@if (DeckSyndicates.Any()) {
@foreach (var syn in DeckSyndicates) { }
}
}
Main Deck
@GroupedDeckCards.Count unique
@if (!deck.Cards.Any()) {
Deck is Empty

Click cards in the browser to add them to your deck.

} else {
@foreach (var group in GroupedDeckCards) { var cardData = allCards.FirstOrDefault(c => c.Name == group.Name); var rowCatClass = cardData?.IsAgent == true ? "row-agent" : cardData?.IsSpell == true ? "row-spell" : cardData?.IsImmortalized == true ? "row-immortalized" : "";
@if (cardData?.Cost.HasValue == true) { @cardData.Cost } @group.Name
@group.Count
}
}
@if (showDiverPicker) { } @if (showSaveToast) { } }
@code { [Parameter] public Guid? DeckId { get; set; } private UserDeck deck = new() { Name = "New Deck", Season = "Season 1" }; private List allCards = []; private List syndicates = []; private string searchFilter = ""; private string categoryFilter = ""; private string syndicateFilter = ""; private string costFilter = ""; private string diverSearch = ""; private bool showDiverPicker; private int activeDiverSlot = 0; private bool isSaving; private bool showSaveToast; private string? errorMessage; private record GroupedCard(string Name, int Count); private List GroupedDeckCards => deck.Cards .GroupBy(c => c) .Select(g => new GroupedCard(g.Key, g.Count())) .OrderBy(g => g.Name) .ToList(); private IEnumerable FilteredBrowserCards => allCards .Where(c => (c.Category is "Agent" or "Spell") && !c.IsImmortalized) .Where(c => string.IsNullOrWhiteSpace(searchFilter) || c.MatchesSearch(searchFilter)) .Where(c => string.IsNullOrEmpty(categoryFilter) || c.Category == categoryFilter) .Where(c => string.IsNullOrEmpty(syndicateFilter) || c.Syndicate == syndicateFilter) .Where(c => string.IsNullOrEmpty(costFilter) || c.Cost?.ToString() == costFilter) .OrderBy(c => c.Cost ?? 99) .ThenBy(c => c.Name); private int AgentsCount => deck.Cards.Count(name => allCards.FirstOrDefault(c => c.Name == name)?.Category == "Agent"); private int SpellsCount => deck.Cards.Count(name => allCards.FirstOrDefault(c => c.Name == name)?.Category == "Spell"); private int ImmortalizedCount => deck.Cards.Count(name => allCards.FirstOrDefault(c => c.Name == name)?.Category == "Immortalized"); private List DeckSyndicates => deck.Cards .Select(name => allCards.FirstOrDefault(c => c.Name == name)?.Syndicate) .Where(s => !string.IsNullOrEmpty(s)) .Distinct() .ToList()!; private List<(int Cost, int Count)> GetManaDistribution() { var costs = deck.Cards .Select(name => allCards.FirstOrDefault(card => card.Name == name)?.Cost ?? 0) .ToList(); var maxCost = 8; return Enumerable.Range(0, maxCost + 1) .Select(cost => (Cost: cost, Count: costs.Count(c => c == cost))) .ToList(); } private static string GetSyndicateDotClass(string? syndicate) { return syndicate?.ToLowerInvariant() switch { "singularity" => "syn-singularity", "phasetide" => "syn-phasetide", "silence" => "syn-silence", "lifeblood" => "syn-lifeblood", "sungrace" => "syn-sungrace", "splintergleam" => "syn-splintergleam", _ => "" }; } private void ResetFilters() { searchFilter = ""; categoryFilter = ""; syndicateFilter = ""; costFilter = ""; } protected override async Task OnInitializedAsync() { allCards = CardDatabase.Cards; syndicates = allCards .Select(c => c.Syndicate) .Where(s => !string.IsNullOrEmpty(s)) .Distinct() .OrderBy(s => s) .ToList()!; AuthService.AuthStateChanged += OnAuthStateChanged; await AuthService.InitializeAsync(); } protected override async Task OnParametersSetAsync() { if (DeckId.HasValue && DeckId.Value != Guid.Empty) { if (deck.Id != DeckId.Value) { var loaded = await UserDecksService.GetDeckAsync(DeckId.Value); if (loaded != null) { deck = loaded; } } } } private void OnAuthStateChanged() { InvokeAsync(StateHasChanged); } private void AddCardToDeck(CardData card) { if (deck.Cards.Count >= 40) return; if (deck.Cards.Count(c => c == card.Name) >= 3) return; if (deck.Divers.Contains(card.Name)) return; deck.Cards.Add(card.Name); } private void AddCardCopy(string cardName) { if (deck.Cards.Count >= 40) return; if (deck.Cards.Count(c => c == cardName) >= 3) return; if (deck.Divers.Contains(cardName)) return; deck.Cards.Add(cardName); } private void RemoveCardCopy(string cardName) { deck.Cards.Remove(cardName); } private void ClearDeck() { deck.Cards.Clear(); } private void OpenDiverPicker(int slot) { activeDiverSlot = slot; diverSearch = ""; showDiverPicker = true; } private void CloseDiverPicker() { showDiverPicker = false; } private List GetAvailableDivers() { var otherDiver = (activeDiverSlot == 0 && deck.Divers.Count > 1) ? deck.Divers[1] : (activeDiverSlot == 1 && deck.Divers.Count > 0) ? deck.Divers[0] : null; return allCards .Where(c => (c.Category is "Agent" or "Spell") && !c.IsImmortalized) .Where(c => !deck.Cards.Contains(c.Name)) // Diver cannot be in deck .Where(c => string.IsNullOrEmpty(otherDiver) || c.Name != otherDiver) // Divers must be unique .Where(c => string.IsNullOrWhiteSpace(diverSearch) || c.MatchesSearch(diverSearch)) .OrderBy(c => c.Name) .ToList(); } private void SelectDiver(string cardName) { while (deck.Divers.Count <= activeDiverSlot) { deck.Divers.Add(""); } deck.Divers[activeDiverSlot] = cardName; deck.Divers = deck.Divers.Where(d => !string.IsNullOrEmpty(d)).ToList(); showDiverPicker = false; } private void RemoveDiver(int slot) { if (slot < deck.Divers.Count) { deck.Divers.RemoveAt(slot); } } private async Task SaveDeckAsync() { if (string.IsNullOrWhiteSpace(deck.Name)) { deck.Name = "Untitled Deck"; } isSaving = true; errorMessage = null; try { var saved = await UserDecksService.SaveDeckAsync(deck); if (saved != null) { var isNew = !DeckId.HasValue || DeckId.Value == Guid.Empty; deck.Id = saved.Id; deck.UpdatedAt = saved.UpdatedAt; showSaveToast = true; if (isNew || DeckId != saved.Id) { DeckId = saved.Id; Navigation.NavigateTo($"/deck-builder/{saved.Id}"); } } else { errorMessage = "Failed to save deck to database."; } } catch (Exception ex) { errorMessage = $"Error saving deck: {ex.Message}"; } finally { isSaving = false; } } public void Dispose() { AuthService.AuthStateChanged -= OnAuthStateChanged; } }