Vibe link and card meta data fixes

This commit is contained in:
2026-06-25 19:27:58 -04:00
parent 73ae3eac53
commit 0005fff01d
8 changed files with 143 additions and 28 deletions
+1 -1
View File
@@ -235,7 +235,7 @@
text-decoration-color: #7b73ff; text-decoration-color: #7b73ff;
} }
@
@media (max-width: 768px) { @media (max-width: 768px) {
.detail-layout { .detail-layout {
flex-direction: column; flex-direction: column;
+71 -12
View File
@@ -26,11 +26,15 @@
@onclick='@(() => SetCategory("Immortalized"))'> @onclick='@(() => SetCategory("Immortalized"))'>
<i class="bi bi-person-fill"></i> Immortalized <i class="bi bi-person-fill"></i> Immortalized
</button> </button>
<!-- //Todo make agent being linked to immortal for side by side sorting
<button class="tab agent @(agentLink ? "active" : "")" @onclick='@(() => ToggleAgentLink())'> <div class="tab link-toggle @(agentLink ? "active" : "")" @onclick="ToggleAgentLink" title="Link Agents and Immortalized cards">
<i class="bi bi-person-fill"></i> Link <i class="bi bi-link-45deg"></i>
</button> <span>Linked</span>
--> <span class="toggle-switch @(agentLink ? "on" : "off")"></span>
</div>
<div class="category-divider"></div>
<button class="tab spell @(categoryFilter == "Spell" ? "active" : "")" @onclick='@(() => SetCategory("Spell"))'> <button class="tab spell @(categoryFilter == "Spell" ? "active" : "")" @onclick='@(() => SetCategory("Spell"))'>
<i class="bi bi-wand"></i> Spells <i class="bi bi-wand"></i> Spells
</button> </button>
@@ -206,21 +210,70 @@
private IEnumerable<CardData> ApplyFilters() private IEnumerable<CardData> ApplyFilters()
{ {
var filtered = allCards.Where(c => var primarySet = allCards.Where(c =>
c.MatchesSearch(search) && c.MatchesSearch(search) &&
(categoryFilter == "" || c.Category == categoryFilter) && (categoryFilter == "" || c.Category == categoryFilter) &&
(factionFilter == "" || c.Faction == factionFilter) && (factionFilter == "" || c.Faction == factionFilter) &&
(costFilter == "" || c.Cost?.ToString() == costFilter) (costFilter == "" || c.Cost?.ToString() == costFilter)
); );
return sortBy switch var sortedPrimary = sortBy switch
{ {
"Cost" => sortDescending ? filtered.OrderByDescending(c => c.Cost) : filtered.OrderBy(c => c.Cost), "Cost" => sortDescending ? primarySet.OrderByDescending(c => c.Cost) : primarySet.OrderBy(c => c.Cost),
"Efficiency" => sortDescending ? filtered.OrderByDescending(c => c.StatEfficiency) : filtered.OrderBy(c => c.StatEfficiency), "Efficiency" => sortDescending ? primarySet.OrderByDescending(c => c.StatEfficiency) : primarySet.OrderBy(c => c.StatEfficiency),
"Attack" => sortDescending ? filtered.OrderByDescending(c => c.Attack) : filtered.OrderBy(c => c.Attack), "Attack" => sortDescending ? primarySet.OrderByDescending(c => c.Attack) : primarySet.OrderBy(c => c.Attack),
"Health" => sortDescending ? filtered.OrderByDescending(c => c.Health) : filtered.OrderBy(c => c.Health), "Health" => sortDescending ? primarySet.OrderByDescending(c => c.Health) : primarySet.OrderBy(c => c.Health),
_ => sortDescending ? filtered.OrderByDescending(c => c.Name) : filtered.OrderBy(c => c.Name) _ => sortDescending ? primarySet.OrderByDescending(c => c.Name) : primarySet.OrderBy(c => c.Name)
}; };
if (!agentLink)
{
return sortedPrimary;
}
var result = new List<CardData>();
var seen = new HashSet<string>();
foreach (var card in sortedPrimary)
{
if (seen.Contains(card.Name)) continue;
// Determine the "Chain Head" (the Agent)
var head = card;
if (card.Category == "Immortalized" && card.ImmortalizeFrom != null)
{
var source = allCards.FirstOrDefault(c => c.Name == card.ImmortalizeFrom);
if (source != null) head = source;
}
// Add the chain starting from head
if (!seen.Contains(head.Name))
{
result.Add(head);
seen.Add(head.Name);
}
if (head.ImmortalizeTo != null)
{
foreach (var targetName in head.ImmortalizeTo)
{
if (seen.Contains(targetName)) continue;
var linked = allCards.FirstOrDefault(c => c.Name == targetName);
if (linked != null)
{
result.Add(linked);
seen.Add(linked.Name);
}
}
}
// Ensure the original card is added if it was somehow not part of the head's chain
if (!seen.Contains(card.Name))
{
result.Add(card);
seen.Add(card.Name);
}
}
return result;
} }
private void SetImmortal(string imm) private void SetImmortal(string imm)
@@ -253,6 +306,12 @@
private void ToggleAgentLink() private void ToggleAgentLink()
{ {
agentLink = !agentLink; agentLink = !agentLink;
OnToggleAgentLink();
}
private void OnToggleAgentLink()
{
_ = AnalyticsService.TrackEvent("toggle_agent_link", new { enabled = agentLink });
} }
private void ClearCostFilter() private void ClearCostFilter()
+56
View File
@@ -74,6 +74,62 @@
font-size: 0.9rem; font-size: 0.9rem;
} }
.category-divider {
width: 1px;
height: 1.5rem;
background: var(--border);
margin: 0 0.6rem;
align-self: center;
opacity: 0.6;
}
.tab.link-toggle {
border-style: dashed;
gap: 0.6rem;
user-select: none;
padding-right: 0.8rem;
margin-left: 0.2rem;
}
.tab.link-toggle.active {
border-style: solid;
background: rgba(108, 99, 255, 0.1);
border-color: var(--accent);
color: var(--text-primary);
}
.toggle-switch {
width: 28px;
height: 16px;
background: var(--bg-hover);
border-radius: 10px;
position: relative;
transition: all 0.2s;
border: 1px solid var(--border);
flex-shrink: 0;
}
.toggle-switch.on {
background: var(--accent);
border-color: rgba(255, 255, 255, 0.2);
}
.toggle-switch::after {
content: '';
position: absolute;
width: 10px;
height: 10px;
background: white;
border-radius: 50%;
top: 2px;
left: 2px;
transition: transform 0.2s;
}
.toggle-switch.on::after {
transform: translateX(12px);
}
/* ── Filter Bar ── */ /* ── Filter Bar ── */
.filter-bar { .filter-bar {
display: flex; display: flex;
+10 -10
View File
@@ -13,12 +13,12 @@
"state": { "state": {
"type": "markdown", "type": "markdown",
"state": { "state": {
"file": "Deck/Overflowing Security.md", "file": "Agent/[[Phasetide]]/Stern Arbiter.md",
"mode": "source", "mode": "source",
"source": false "source": false
}, },
"icon": "lucide-file", "icon": "lucide-file",
"title": "Overflowing Security" "title": "Stern Arbiter"
} }
} }
] ]
@@ -53,7 +53,7 @@
"state": { "state": {
"type": "search", "type": "search",
"state": { "state": {
"query": "The Ripper", "query": "...",
"matchingCase": false, "matchingCase": false,
"explainSearch": false, "explainSearch": false,
"collapseAll": false, "collapseAll": false,
@@ -74,12 +74,12 @@
"title": "Bookmarks" "title": "Bookmarks"
} }
} }
] ],
"currentTab": 1
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 331.5, "width": 331.5
"collapsed": true
}, },
"right": { "right": {
"id": "47f3bcd7aed212ab", "id": "47f3bcd7aed212ab",
@@ -186,8 +186,11 @@
}, },
"active": "852a1aaf6b54ec43", "active": "852a1aaf6b54ec43",
"lastOpenFiles": [ "lastOpenFiles": [
"Deck/Big Energy.md", "Immortalized/[[Sungrace]]/Time Devourer Soval.md",
"Spell/[[Phasetide]]/By the Numbers.md",
"Immortalized/[[Splintergleam]]/Boatswain Corvus.md",
"Deck/Overflowing Security.md", "Deck/Overflowing Security.md",
"Deck/Big Energy.md",
"Agent/[[Lifeblood]]/Aardvark Precinct Captain.md", "Agent/[[Lifeblood]]/Aardvark Precinct Captain.md",
"Agent/[[Lifeblood]]/Egg Tender.md", "Agent/[[Lifeblood]]/Egg Tender.md",
"Quest/Daily Wins.md", "Quest/Daily Wins.md",
@@ -212,9 +215,6 @@
"Rule/Fast.md", "Rule/Fast.md",
"_Store.base", "_Store.base",
"_Timeline.base", "_Timeline.base",
"Store/Core Set Packs x1.md",
"Store/Core Set Packs x5.md",
"Store/Core Set Packs x10.md",
"_Quests.base", "_Quests.base",
"Quest", "Quest",
"Store", "Store",
@@ -6,8 +6,8 @@ rarity: Lost
cost: 7 cost: 7
attack: 5 attack: 5
health: 5 health: 5
description: "[[Play]]: Return an Action from your Graveyard to your hand. It costs 3 less and has \"If I would be put into a Graveyard, instead [[Erase]] me.\"." description: '[[Play]]: Return an Action from your Graveyard to your hand. It costs 3 less and has "If I would be put into a Graveyard, instead [[Erase]] me.".'
immortalizeWhen: "I've seen you play 3+ cards wi..." immortalizeWhen: I've seen you play 3+ cards with reduced cost.
immortalizeTo: immortalizeTo:
- "[[Grand Judge Dhael]]" - "[[Grand Judge Dhael]]"
immortalizeFrom: N/A immortalizeFrom: N/A
@@ -6,7 +6,7 @@ rarity: Lost
cost: 7 cost: 7
attack: 6 attack: 6
health: 10 health: 10
description: "[[Overpower]]. Round Start: Deal 1 to the enemy Core twice. When you deal non-combat damage to the enemy Core, grant the weakest enemy Exposed. [[Core Strike]]: Deal 3 to all enemies and t..." description: "[[Overpower]]. Round Start: Deal 1 to the enemy Core twice. When you deal non-combat damage to the enemy Core, grant the weakest enemy Exposed. [[Core Strike]]: Deal 3 to all enemies and the enemy Core.]]"
immortalizeWhen: N/A immortalizeWhen: N/A
immortalizeTo: immortalizeTo:
- N/A - N/A
@@ -6,7 +6,7 @@ rarity: Lost
cost: 10 cost: 10
attack: 12 attack: 12
health: 12 health: 12
description: "For each Timeline in the Timeline Stack, I gain a random positive keyword. The first time each round I would be destroyed, instead heal me to my maximum durability and destroy the..." description: For each Timeline in the Timeline Stack, I gain a random positive keyword. The first time each round I would be destroyed, instead heal me to my maximum durability and destroy the current Timeline.
immortalizeWhen: N/A immortalizeWhen: N/A
immortalizeTo: immortalizeTo:
- N/A - N/A
@@ -4,7 +4,7 @@ category: Spell
artist: Bi Pi artist: Bi Pi
rarity: Common rarity: Common
cost: 2 cost: 2
description: "Reveal the top card of both decks. Create a copy of the card with the higher cost in hand, then shuffle both cards into their respective decks. In a tie, create a copy of both card..." description: Reveal the top card of both decks. Create a copy of the card with the higher cost in hand, then shuffle both cards into their respective decks. In a tie, create a copy of both cards.
faction: "[[Phasetide]]" faction: "[[Phasetide]]"
set: "[[Core Set]]" set: "[[Core Set]]"
speed: "[[Immediate]]" speed: "[[Immediate]]"