Compare commits
2 Commits
master
..
50f3d36083
| Author | SHA1 | Date | |
|---|---|---|---|
| 50f3d36083 | |||
| 50dcc8e55c |
@@ -17,7 +17,7 @@ if (!Directory.Exists(docsDir))
|
||||
return 1;
|
||||
}
|
||||
|
||||
var mdFiles = Directory.GetFiles(docsDir, "*.md", SearchOption.AllDirectories);
|
||||
var mdFiles = Directory.GetFiles(docsDir, "*.md");
|
||||
var cards = new List<CardData>();
|
||||
|
||||
foreach (var file in mdFiles)
|
||||
@@ -35,7 +35,7 @@ foreach (var file in mdFiles)
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var category = yaml.GetValueOrDefault("category");
|
||||
if (category == null || category == "Deck") continue;
|
||||
if (category == null) continue;
|
||||
|
||||
var imageFile = StripWikiLink(yaml.GetValueOrDefault("imageLink"));
|
||||
if (imageFile != null && !imageFile.EndsWith(".png"))
|
||||
@@ -67,20 +67,13 @@ foreach (var file in mdFiles)
|
||||
// Copy PNGs to wwwroot/cards
|
||||
var cardsDir = Path.Combine(webWwwRoot, "cards");
|
||||
Directory.CreateDirectory(cardsDir);
|
||||
|
||||
var pngFiles = Directory.GetFiles(docsDir, "*.png", SearchOption.AllDirectories);
|
||||
var pngMap = pngFiles
|
||||
.GroupBy(Path.GetFileName)
|
||||
.ToDictionary(g => g.Key!, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var card in cards)
|
||||
{
|
||||
if (card.ImageFile == null) continue;
|
||||
if (pngMap.TryGetValue(card.ImageFile, out var src))
|
||||
{
|
||||
var dst = Path.Combine(cardsDir, card.ImageFile);
|
||||
var src = Path.Combine(docsDir, card.ImageFile);
|
||||
var dst = Path.Combine(cardsDir, card.ImageFile);
|
||||
if (File.Exists(src))
|
||||
File.Copy(src, dst, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate C# source file
|
||||
@@ -127,7 +120,7 @@ Console.WriteLine($"Generated {cards.Count} cards in {generatedFile}");
|
||||
|
||||
// ── Decks ──
|
||||
var decksDir = Path.Combine(docsDir, "Decks");
|
||||
var deckFiles = Directory.Exists(decksDir) ? Directory.GetFiles(decksDir, "*.md", SearchOption.AllDirectories) : [];
|
||||
var deckFiles = Directory.Exists(decksDir) ? Directory.GetFiles(decksDir, "*.md") : [];
|
||||
var decks = new List<DeckData>();
|
||||
|
||||
foreach (var file in deckFiles)
|
||||
@@ -142,7 +135,6 @@ foreach (var file in deckFiles)
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var isVisible = yaml.GetValueOrDefault("isVisible") == "true";
|
||||
var deckCode = yaml.GetValueOrDefault("deckCode");
|
||||
|
||||
var deck = new DeckData
|
||||
{
|
||||
@@ -153,8 +145,7 @@ foreach (var file in deckFiles)
|
||||
Description = StripWikiLinks(NullIfNa(yaml.GetValueOrDefault("description")))?.Replace("\r\n", "\n")
|
||||
.Replace("\r", "\n").Replace("\n\n", "\n").Replace("\n\n", "\n"),
|
||||
Factions = ParseList(yaml, "factions").Select(s => StripWikiLink(s) ?? "").Where(s => s != "").ToList(),
|
||||
IsVisible = isVisible,
|
||||
DeckCode = deckCode
|
||||
IsVisible = isVisible
|
||||
};
|
||||
|
||||
decks.Add(deck);
|
||||
@@ -188,7 +179,6 @@ for (var i = 0; i < decks.Count; i++)
|
||||
WriteStrProp(deckWriter, "Description", d.Description, 3);
|
||||
WriteListProp(deckWriter, "Factions", d.Factions, 3);
|
||||
deckWriter.WriteLine($" IsVisible = {(d.IsVisible ? "true" : "false")},");
|
||||
WriteStrProp(deckWriter, "DeckCode", d.DeckCode, 3);
|
||||
var comma = i < decks.Count - 1 ? "," : "";
|
||||
deckWriter.WriteLine($" }}{comma}");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class CardData
|
||||
public string? ImmortalizeFrom { get; init; }
|
||||
public string? ImmortalizeWhen { get; init; }
|
||||
public string? ImageFile { get; init; }
|
||||
|
||||
|
||||
public bool IsAgent => Category == "Agent";
|
||||
public bool IsSpell => Category == "Spell";
|
||||
public bool IsToken => Category == "Token";
|
||||
@@ -39,8 +39,6 @@ public class CardData
|
||||
return Name.ToLowerInvariant().Contains(q) ||
|
||||
(Description?.ToLowerInvariant().Contains(q) ?? false) ||
|
||||
(Faction?.ToLowerInvariant().Contains(q) ?? false) ||
|
||||
Archetypes.Any(a => a.ToLowerInvariant().Contains(q)) ||
|
||||
(Name.ToLowerInvariant().Contains("b.o.o.f.") && q.Equals("boof")) ||
|
||||
(Name.ToLowerInvariant().Contains("boof") && q.Equals("b.o.o.f."));
|
||||
Archetypes.Any(a => a.ToLowerInvariant().Contains(q));
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,6 @@ public class DeckData
|
||||
public List<string> Keycards { get; init; } = [];
|
||||
public List<string> Divers { get; init; } = [];
|
||||
public string? Description { get; init; }
|
||||
|
||||
public string? DeckCode { get; init; }
|
||||
|
||||
public List<string> Factions { get; init; } = [];
|
||||
public bool IsVisible { get; init; }
|
||||
|
||||
|
||||
@@ -6,4 +6,42 @@ namespace Tests;
|
||||
[TestFixture]
|
||||
public class PlaywrightTests : PageTest
|
||||
{
|
||||
[Test]
|
||||
public async Task CanWriteAndSaveNote()
|
||||
{
|
||||
// 1. Navigate to the cards gallery
|
||||
await Page.GotoAsync("http://localhost:8080/cards");
|
||||
|
||||
// 2. Wait for cards to load - looking for at least one card-cell
|
||||
await Expect(Page.Locator(".card-cell").First).ToBeVisibleAsync();
|
||||
|
||||
// 3. Find an Agent card and click it.
|
||||
var agentCard = Page.Locator(".card-cell:has(.card-category-badge.agent)").First;
|
||||
await agentCard.ClickAsync();
|
||||
|
||||
// 4. Wait for the detail view to show the note textarea
|
||||
var noteInput = Page.Locator(".note-input");
|
||||
await Expect(noteInput).ToBeVisibleAsync();
|
||||
|
||||
// 5. Type a unique note
|
||||
var uniqueNote = "Test note " + Guid.NewGuid();
|
||||
await noteInput.FillAsync(uniqueNote);
|
||||
|
||||
// 6. Blur to trigger save
|
||||
await noteInput.BlurAsync();
|
||||
|
||||
// 7. Wait for saving indicator to disappear (if it appeared)
|
||||
var savingIndicator = Page.Locator(".saving-indicator");
|
||||
if (await savingIndicator.IsVisibleAsync()) await Expect(savingIndicator).Not.ToBeVisibleAsync();
|
||||
|
||||
// 8. Close the detail view by clicking the backdrop
|
||||
await Page.Locator(".modal-backdrop").ClickAsync();
|
||||
await Expect(noteInput).Not.ToBeVisibleAsync();
|
||||
|
||||
// 9. Re-open the same agent card
|
||||
await agentCard.ClickAsync();
|
||||
|
||||
// 10. Verify the note is still there
|
||||
await Expect(noteInput).ToHaveValueAsync(uniqueNote);
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,95 @@ public static class DeckDatabase
|
||||
{
|
||||
public static readonly System.Collections.Generic.List<DeckData> Decks =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "Big Energy",
|
||||
Cards = [
|
||||
"Brilliant Martyr",
|
||||
"Brilliant Martyr",
|
||||
"Brilliant Martyr",
|
||||
"Kinetic Absorber",
|
||||
"Kinetic Absorber",
|
||||
"Kinetic Absorber",
|
||||
"Hidden Locus",
|
||||
"Hidden Locus",
|
||||
"Hidden Locus",
|
||||
"Suncursed Conduit",
|
||||
"Suncursed Conduit",
|
||||
"Suncursed Conduit",
|
||||
"Swashbuckling Diehard",
|
||||
"Swashbuckling Diehard",
|
||||
"Swashbuckling Diehard",
|
||||
"Debris Collector",
|
||||
"Debris Collector",
|
||||
"Debris Collector",
|
||||
"Paradox Flow",
|
||||
"Paradox Flow",
|
||||
"Starfueled Medics",
|
||||
"Starfueled Medics",
|
||||
"Starfueled Medics",
|
||||
"Lumbering Starseeker",
|
||||
"Lumbering Starseeker",
|
||||
"Novathermal Mining",
|
||||
"Novathermal Mining",
|
||||
"Novathermal Mining",
|
||||
"Radiant Channeling",
|
||||
"Radiant Channeling",
|
||||
"Radiant Channeling",
|
||||
"Supernova",
|
||||
"Supernova",
|
||||
"Supernova",
|
||||
"Gunnery Captain",
|
||||
"Lightsteel Colossus",
|
||||
"Lightsteel Colossus",
|
||||
"Devourer Spawn",
|
||||
"Devourer Spawn",
|
||||
"Army of the Sun",
|
||||
],
|
||||
Keycards = [
|
||||
"Kinetic Absorber",
|
||||
"Hidden Locus",
|
||||
"Debris Collector",
|
||||
"Lumbering Starseeker",
|
||||
"Lightsteel Colossus",
|
||||
],
|
||||
Divers = [
|
||||
"Peaceful Synthesizer",
|
||||
"Limit Breaker",
|
||||
],
|
||||
Description = "The idea of this deck is to go heavy on stat-efficient cards. Like 2 for a 2/3 Kinetic Absorber, 5/6 Lumbering Starseeker, and 8 for a 9/14 Lightsteel Colossus. Debris Collector is also quite powerful and will Immortalize into a Living Comet 10/10 with Overpower pretty reliably, but it's also your only stat Mute target, so it might be a bit trash if that's popular in the meta.\nYou got Devourer Spawn to help push for lethal. It's going to have a bunch of keywords on it, given how popular timelines are in the meta.\nYou need to Overflow your mana once in a while, or some of your Supernova removal will be useless. Be pass heavy.\nYou're going to win on very low health if you win. Such is life.\nNo card draw, so Army of the Sun is your hope if things go long. At the worst, it's a spell that summons a big unit right away if it goes off.\nStarfueled Medics and Swashbuckling Diehard are just there to take up space and be mid-range drops. And technically, they can be win conditions, I suppose.\nAttack aggressively with Brilliant Martyr. You really want Star Siphon to ramp.\nAs divers, Peaceful Synthesizer is a 1 drop that will quickly turn into a 3/3. Can't get more stat efficient than that. And Limit Breaker is an early-game growing threat that will demand an answer from the enemy.",
|
||||
Factions = [
|
||||
"Sungrace",
|
||||
],
|
||||
IsVisible = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Rewind Me",
|
||||
Cards = [
|
||||
"Curious Acolyte",
|
||||
"Chronicle of the One",
|
||||
"Prayer of Rescue",
|
||||
"Backhand",
|
||||
"Snap Back",
|
||||
"Sunbringer Artillerist",
|
||||
"Sunshock",
|
||||
"Temple Analyst",
|
||||
"Holy Cleaner",
|
||||
"Balance Blade",
|
||||
"Rescind Authorization",
|
||||
"Out of Line",
|
||||
"Divergence Assassin",
|
||||
"Chronal Quarantine",
|
||||
"Holder of the Instruments",
|
||||
],
|
||||
Keycards = [
|
||||
],
|
||||
Divers = [
|
||||
],
|
||||
Factions = [
|
||||
],
|
||||
IsVisible = false,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,21 +13,15 @@
|
||||
<button class="tab @(categoryFilter == "" ? "active" : "")" @onclick='@(() => SetCategory(""))'>
|
||||
<i class="bi bi-grid-3x3-gap-fill"></i> All
|
||||
</button>
|
||||
|
||||
<button class="tab agent @(categoryFilter == "Agent" ? "active" : "")" @onclick='@(() => SetCategory("Agent"))'>
|
||||
<i class="bi bi-person-fill"></i> Agents
|
||||
</button>
|
||||
<button class="tab agent @(categoryFilter == "Immortalized" ? "active" : "")" @onclick='@(() => SetCategory("Immortalized"))'>
|
||||
<i class="bi bi-person-fill"></i> Immortalized
|
||||
</button>
|
||||
<!-- //Todo make agent being linked to immortal for side by side sorting
|
||||
<button class="tab agent @(agentLink ? "active" : "")" @onclick='@(() => ToggleAgentLink())'>
|
||||
<i class="bi bi-person-fill"></i> Link
|
||||
</button>
|
||||
-->
|
||||
<button class="tab spell @(categoryFilter == "Spell" ? "active" : "")" @onclick='@(() => SetCategory("Spell"))'>
|
||||
<i class="bi bi-wand"></i> Spells
|
||||
</button>
|
||||
<button class="tab token @(categoryFilter == "Token" ? "active" : "")" @onclick='@(() => SetCategory("Token"))'>
|
||||
<i class="bi bi-coin"></i> Tokens
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
@@ -49,15 +43,15 @@
|
||||
</select>
|
||||
<select @bind="costFilter" class="form-select filter-select">
|
||||
<option value="">All Costs</option>
|
||||
@for (var i = 0; i <= 13; i++)
|
||||
@for (var i = 0; i <= 12; i++)
|
||||
{
|
||||
<option value="@i">@i</option>
|
||||
}
|
||||
</select>
|
||||
<div class="sort-group d-flex gap-1">
|
||||
<select @bind="sortBy" class="form-select filter-select sort-select">
|
||||
<option value="Cost">Sort by Cost</option>
|
||||
<option value="Name">Sort by Name</option>
|
||||
<option value="Cost">Sort by Cost</option>
|
||||
<option value="Attack">Sort by Attack</option>
|
||||
<option value="Health">Sort by Health</option>
|
||||
<option value="Efficiency">Sort by Efficiency</option>
|
||||
@@ -71,6 +65,10 @@
|
||||
@onclick="() => showDetailedView = !showDetailedView" title="Toggle Detailed View">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-warning random-deck-btn" @onclick="GenerateRandomDeck"
|
||||
title="Generate Random Deck">
|
||||
<i class="bi bi-dice-5-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (HasActiveFilters)
|
||||
@@ -114,7 +112,7 @@
|
||||
{
|
||||
<div class="card-grid @(showDetailedView ? "detailed-view" : "")">
|
||||
@{ var idx = 0; }
|
||||
@foreach (var card in filteredCards.Where(a=>a.Category is "Agent" or "Spell" or "Immortalized"))
|
||||
@foreach (var card in filteredCards)
|
||||
{
|
||||
<div class="card-cell @(selectedCard == card ? "selected" : "")"
|
||||
style="--i: @idx"
|
||||
@@ -123,16 +121,20 @@
|
||||
<div class="card-shimmer"></div>
|
||||
<img src="@card.ImagePath" alt="@card.Name" loading="lazy"
|
||||
onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22200%22 height=%22280%22><rect fill=%22%23222244%22 width=%22200%22 height=%22280%22/><text fill=%22%23686888%22 font-size=%2214%22 x=%22100%22 y=%22140%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22>No Image</text></svg>'"/>
|
||||
@if (card.IsImmortalized)
|
||||
@if (card.Cost.HasValue)
|
||||
{
|
||||
<div class="card-cost-badge">@card.Cost</div>
|
||||
}
|
||||
@if (card.HasImmortalize)
|
||||
{
|
||||
<div class="card-immortalize-badge" title="Immortalizes"><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (showDetailedView)
|
||||
{
|
||||
<div class="card-label">
|
||||
<div class="card-name">@card.Name</div>
|
||||
<div class="card-label">
|
||||
<div class="card-name">@card.Name</div>
|
||||
@if (showDetailedView)
|
||||
{
|
||||
<div class="card-stats">
|
||||
@if (card.Attack.HasValue)
|
||||
{
|
||||
@@ -144,10 +146,9 @@
|
||||
}
|
||||
</div>
|
||||
<div class="card-description-preview">@card.Description</div>
|
||||
<div class="card-category-badge @card.Category?.ToLowerInvariant()">@card.Category</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="card-category-badge @card.Category?.ToLowerInvariant()">@card.Category</div>
|
||||
</div>
|
||||
</div>
|
||||
idx++;
|
||||
}
|
||||
@@ -269,12 +270,9 @@
|
||||
private IEnumerable<CardData> filteredCards => ApplyFilters();
|
||||
private string search = "";
|
||||
private string categoryFilter = "";
|
||||
private string immortalFilter = "";
|
||||
private bool agentOnly = true;
|
||||
private bool agentLink = true;
|
||||
private string factionFilter = "";
|
||||
private string costFilter = "";
|
||||
private string sortBy = "Cost";
|
||||
private string sortBy = "Name";
|
||||
private bool sortDescending;
|
||||
private bool showDetailedView;
|
||||
private CardData? selectedCard;
|
||||
@@ -282,7 +280,7 @@
|
||||
private string currentNote = "";
|
||||
private bool isSaving;
|
||||
|
||||
private bool HasActiveFilters => categoryFilter != "" || factionFilter != "" || costFilter != "" || immortalFilter != "";
|
||||
private bool HasActiveFilters => categoryFilter != "" || factionFilter != "" || costFilter != "";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -313,16 +311,6 @@
|
||||
_ => sortDescending ? filtered.OrderByDescending(c => c.Name) : filtered.OrderBy(c => c.Name)
|
||||
};
|
||||
}
|
||||
|
||||
private void SetImmortal(string imm)
|
||||
{
|
||||
immortalFilter = immortalFilter == imm ? "" : imm;
|
||||
}
|
||||
|
||||
private void ClearImmortalFilter()
|
||||
{
|
||||
immortalFilter = "";
|
||||
}
|
||||
|
||||
private void SetCategory(string cat)
|
||||
{
|
||||
@@ -339,11 +327,6 @@
|
||||
factionFilter = "";
|
||||
}
|
||||
|
||||
private void ToggleAgentLink()
|
||||
{
|
||||
agentLink = !agentLink;
|
||||
}
|
||||
|
||||
private void ClearCostFilter()
|
||||
{
|
||||
costFilter = "";
|
||||
@@ -403,4 +386,27 @@
|
||||
factionFilter = "";
|
||||
costFilter = "";
|
||||
}
|
||||
|
||||
private void GenerateRandomDeck()
|
||||
{
|
||||
var random = new Random();
|
||||
var pool = allCards.Where(c => !c.IsToken && !string.IsNullOrEmpty(c.Faction)).ToList();
|
||||
if (pool.Count < 40) return;
|
||||
|
||||
var selectedFaction = factions[random.Next(factions.Count)];
|
||||
var factionPool = pool.Where(c => c.Faction == selectedFaction || c.Faction == "Neutral").ToList();
|
||||
|
||||
// Simple logic: pick 40 cards
|
||||
var deckCards = factionPool.OrderBy(x => random.Next()).Take(40).Select(c => c.Name).ToList();
|
||||
|
||||
// For now, let's just log it or we could navigate to a "Create Deck" page if it existed.
|
||||
// Since we don't have a create deck page in the context, let's just show a notification or similar.
|
||||
// Actually, I'll just clear filters and search for these cards to "show" them.
|
||||
search = string.Join(" | ", deckCards.Take(3)); // Just show a few to demonstrate
|
||||
categoryFilter = "";
|
||||
factionFilter = selectedFaction;
|
||||
|
||||
// In a real app, this would save to a new DeckData object.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -727,16 +727,8 @@
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 0.75rem;
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.card-grid.detailed-view {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.detail-layout {
|
||||
@@ -761,10 +753,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card-grid, .card-grid.detailed-view {
|
||||
grid-template-columns: 1fr;
|
||||
.card-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
gap: 0.5rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<article class="deck-article">
|
||||
<header class="deck-header">
|
||||
<h1>@deck.Name</h1>
|
||||
<b>Deck Code:</b> <pre>@deck.DeckCode</pre>
|
||||
@if (deck.Factions.Count > 0)
|
||||
{
|
||||
<div class="deck-factions">
|
||||
@@ -112,7 +111,10 @@
|
||||
<section class="deck-section description">
|
||||
<h2><i class="bi bi-chat-quote-fill"></i> Description</h2>
|
||||
<div class="deck-description">
|
||||
@RenderDescription(deck.Description)
|
||||
@foreach (var paragraph in deck.Description.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
<p>@paragraph</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
@@ -236,65 +238,4 @@
|
||||
selectedCard = null;
|
||||
}
|
||||
|
||||
private RenderFragment RenderDescription(string description)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
var cardNames = CardDatabase.Cards
|
||||
.Select(c => c.Name)
|
||||
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||
.OrderByDescending(n => n.Length)
|
||||
.ToList();
|
||||
|
||||
int sequence = 0;
|
||||
var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var paragraph in paragraphs)
|
||||
{
|
||||
builder.OpenElement(sequence++, "p");
|
||||
|
||||
var currentText = paragraph;
|
||||
while (!string.IsNullOrEmpty(currentText))
|
||||
{
|
||||
int bestMatchIndex = -1;
|
||||
string? bestMatchName = null;
|
||||
|
||||
foreach (var name in cardNames)
|
||||
{
|
||||
int index = currentText.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
||||
if (index != -1 && (bestMatchIndex == -1 || index < bestMatchIndex))
|
||||
{
|
||||
bestMatchIndex = index;
|
||||
bestMatchName = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatchIndex != -1 && bestMatchName != null)
|
||||
{
|
||||
if (bestMatchIndex > 0)
|
||||
{
|
||||
builder.AddContent(sequence++, currentText.Substring(0, bestMatchIndex));
|
||||
}
|
||||
|
||||
var card = LookupCard(bestMatchName);
|
||||
builder.OpenElement(sequence++, "button");
|
||||
builder.AddAttribute(sequence++, "class", "inline-card-btn");
|
||||
builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => SelectCard(card)));
|
||||
builder.AddContent(sequence++, bestMatchName);
|
||||
builder.CloseElement();
|
||||
|
||||
currentText = currentText.Substring(bestMatchIndex + bestMatchName.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddContent(sequence++, currentText);
|
||||
currentText = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
|
||||
.deck-description p {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<div class="decks-header">
|
||||
<h1>Decks</h1>
|
||||
<p class="text-secondary">@decks.Count deck@(decks.Count != 1 ? "s" : "")</p>
|
||||
<p class="text-primary">Please note these are my crafted decks. They are probably not optimal or good. See <a href="https://runeterra.ar/chrono/home">Runeterra.ar</a> and check out the decks from recognizable names there. This is not an ad, just common sense.</p>
|
||||
</div>
|
||||
|
||||
<div class="deck-list">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<div class="home-hero">
|
||||
<div class="hero-glow"></div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-badge">Collectible Card Game</div>
|
||||
<h1 class="hero-title">Slop Game Reference - Chrono CCG</h1>
|
||||
<p class="hero-subtitle">
|
||||
AI generated website for reference notes on playing Chrono CCG.
|
||||
@@ -13,13 +14,113 @@
|
||||
<a class="cta-button primary" href="/cards">
|
||||
<i class="bi bi-collection-fill"></i> Browse Cards
|
||||
</a>
|
||||
<a class="cta-button secondary" href="/agents">
|
||||
<i class="bi bi-people-fill"></i> Agents Data Table
|
||||
</a>
|
||||
<a class="cta-button secondary" href="/decks">
|
||||
<i class="bi bi-journal-text"></i> View Decks
|
||||
</a>
|
||||
<a class="cta-button secondary" href="/agents">
|
||||
<i class="bi bi-people-fill"></i> All Agents
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="home-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon agents">
|
||||
<i class="bi bi-person-fill"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@agentsCount</span>
|
||||
<span class="stat-label">Agents</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon spells">
|
||||
<i class="bi bi-wand"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@spellsCount</span>
|
||||
<span class="stat-label">Spells</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon tokens">
|
||||
<i class="bi bi-coin"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@tokensCount</span>
|
||||
<span class="stat-label">Tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon decks">
|
||||
<i class="bi bi-journal-text"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@visibleDecks</span>
|
||||
<span class="stat-label">Decks</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon factions">
|
||||
<i class="bi bi-shield-fill"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@factionCount</span>
|
||||
<span class="stat-label">Factions</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon total">
|
||||
<i class="bi bi-grid-3x3-gap-fill"></i>
|
||||
</div>
|
||||
<div class="stat-body">
|
||||
<span class="stat-value">@totalCount</span>
|
||||
<span class="stat-label">Total Cards</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="home-sections">
|
||||
<div class="section-card">
|
||||
<div class="section-icon"><i class="bi bi-collection-fill"></i></div>
|
||||
<h2>Explore Cards</h2>
|
||||
<p>Browse the full gallery of @totalCount cards. Filter by faction, cost, or category, and dive into detailed
|
||||
stats, archetypes, and immortalize chains.</p>
|
||||
<a href="/cards" class="section-link">Browse Gallery <i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
<div class="section-card">
|
||||
<div class="section-icon"><i class="bi bi-people-fill"></i></div>
|
||||
<h2>View Agents</h2>
|
||||
<p>All @agentsCount agents in a sortable data grid with filtering, paging, and detailed stats at a glance.</p>
|
||||
<a href="/agents" class="section-link">View Agents <i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
<div class="section-card">
|
||||
<div class="section-icon"><i class="bi bi-journal-text"></i></div>
|
||||
<h2>Browse Decks</h2>
|
||||
<p>Check out @visibleDecks curated deck lists. See key cards, divers, and full card breakdowns with interactive
|
||||
previews.</p>
|
||||
<a href="/decks" class="section-link">Browse Decks <i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private int totalCount;
|
||||
private int agentsCount;
|
||||
private int spellsCount;
|
||||
private int tokensCount;
|
||||
private int visibleDecks;
|
||||
private int factionCount;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var cards = CardDatabase.Cards;
|
||||
totalCount = cards.Count;
|
||||
agentsCount = cards.Count(c => c.IsAgent);
|
||||
spellsCount = cards.Count(c => c.IsSpell);
|
||||
tokensCount = cards.Count(c => c.IsToken);
|
||||
visibleDecks = DeckDatabase.Decks.Count(d => d.IsVisible);
|
||||
factionCount = cards.Where(c => c.Faction != null).Select(c => c.Faction).Distinct().Count();
|
||||
}
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 136 KiB |
@@ -7,7 +7,6 @@
|
||||
--text-secondary: #9898b8;
|
||||
--text-muted: #686888;
|
||||
--accent: #6c63ff;
|
||||
--accent-rgb: 108, 99, 255;
|
||||
--accent-glow: rgba(108, 99, 255, 0.3);
|
||||
--gold: #ffd700;
|
||||
--gold-glow: rgba(255, 215, 0, 0.4);
|
||||
@@ -255,29 +254,3 @@ a, .btn-link {
|
||||
font-style: italic;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.inline-card-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0 2px;
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dashed var(--accent);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.inline-card-btn:hover {
|
||||
color: var(--accent-light, #60a5fa);
|
||||
border-bottom-style: solid;
|
||||
background-color: rgba(var(--accent-rgb, 59, 130, 246), 0.1);
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
|
||||
<link href="/lib/bootstrap-icons/bootstrap-icons.min.css" rel="stylesheet"/>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"/>
|
||||
<link href="/lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
<link href="/_content/Telerik.UI.for.Blazor/css/kendo-theme-bootstrap/all.css" rel="stylesheet"/>
|
||||
<link href="/css/app.css" rel="stylesheet"/>
|
||||
@@ -40,6 +40,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a class="reload" href=".">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<script src="/_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"navigationFallback": {
|
||||
"rewrite": "/index.html",
|
||||
"exclude": ["/css/*", "/lib/*", "/_framework/*", "/_content/*", "/sample-data/*"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# Chrono CCG - Project Guide
|
||||
|
||||
This project is a hosted Blazor WebAssembly application with a PostgreSQL database for persisting agent notes.
|
||||
|
||||
## Prerequisites
|
||||
- **Docker Desktop**: Required for the recommended containerized setup.
|
||||
- **.NET 10 SDK**: Required if you want to build or run the project locally without Docker.
|
||||
|
||||
## 1. Running with Docker (Recommended)
|
||||
The easiest way to get everything running (App + PostgreSQL) is using Docker Compose.
|
||||
|
||||
1. **Open a terminal** in the project root (`Chrono/`).
|
||||
2. **Run the following command**:
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
3. **Access the Application**:
|
||||
- Web Interface: http://localhost:8080
|
||||
- API Endpoint: http://localhost:8080/api/notes
|
||||
|
||||
The database will be automatically initialized and migrations will be applied on startup.
|
||||
|
||||
## 2. Running Locally (Development)
|
||||
If you need to run the app directly (e.g., for faster debugging):
|
||||
|
||||
1. **Start a PostgreSQL database**. You can use the one from docker-compose if you want:
|
||||
```bash
|
||||
docker-compose up db
|
||||
```
|
||||
2. **Verify Connection String**: `Server/appsettings.Development.json` is pre-configured to point to `localhost`.
|
||||
3. **Run the Server project**:
|
||||
```bash
|
||||
cd Server
|
||||
dotnet run --launch-profile https
|
||||
```
|
||||
4. The app will be served at the URL shown in the terminal (e.g., https://localhost:7266).
|
||||
|
||||
## 3. Running Tests
|
||||
To verify the core domain logic:
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
|
||||
## 4. Key Features
|
||||
- **Agent Notes**: In the "Cards" gallery, select an Agent to see the "Personal Note" field. Changes are auto-saved to the PostgreSQL database when you click away from the text area.
|
||||
- **Auto-Migrations**: The Server project automatically handles database schema updates on startup.
|
||||
- **Dockerized Architecture**: Complete orchestration of the web server and database.
|
||||
@@ -1,3 +1 @@
|
||||
{
|
||||
"alwaysUpdateLinks": true
|
||||
}
|
||||
{}
|
||||
@@ -1,3 +1 @@
|
||||
[
|
||||
"frontmatter-folder-organizer"
|
||||
]
|
||||
[]
|
||||
@@ -1,6 +0,0 @@
|
||||
/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
|
||||
"use strict";var d=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var w=(i,t)=>{for(var e in t)d(i,e,{get:t[e],enumerable:!0})},z=(i,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of p(t))!y.call(i,r)&&r!==e&&d(i,r,{get:()=>t[r],enumerable:!(n=F(t,r))||n.enumerable});return i};var S=i=>z(d({},"__esModule",{value:!0}),i);var b={};w(b,{default:()=>m});module.exports=S(b);var g=require("obsidian");var l=require("obsidian"),f={frontmatterHierarchy:"Category, Faction"},h=class extends l.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}display(){let{containerEl:t}=this;t.empty(),t.createEl("h2",{text:"Frontmatter Folder Organizer"}),new l.Setting(t).setName("Frontmatter hierarchy").setDesc("Enter frontmatter keys in order. Files will be organized into folders based on these values.").addTextArea(e=>e.setPlaceholder("category").setValue(this.plugin.settings.frontmatterHierarchy).onChange(async n=>{this.plugin.settings.frontmatterHierarchy=n,await this.plugin.saveSettings()})),new l.Setting(t).setName("Organize files now").setDesc("Create folders and move markdown files according to the configured frontmatter hierarchy.").addButton(e=>e.setButtonText("Organize now").onClick(async()=>{await this.plugin.organizeFilesByFrontmatter()}))}};var m=class extends g.Plugin{async onload(){await this.loadSettings(),this.addCommand({id:"organize-frontmatter-folders",name:"Organize markdown by frontmatter hierarchy",callback:async()=>{await this.organizeFilesByFrontmatter()}}),this.addSettingTab(new h(this.app,this))}async organizeFilesByFrontmatter(){let t=this.getHierarchyKeys();if(!t.length){new g.Notice("Set a frontmatter hierarchy in plugin settings before organizing files.");return}let e=this.app.vault.getFiles().filter(r=>r.extension==="md"),n=0;for(let r of e){let a=this.getFolderSegmentsForFile(r,t);if(!a||!a.length)continue;let s=a.join("/");if(`${s}/${r.name}`===r.path)continue;await this.ensureFolderExists(s);let c=await this.getAvailableTargetPath(s,r.name);await this.app.vault.rename(r,c),n+=1}new g.Notice(`Organized ${n} markdown file${n===1?"":"s"} by frontmatter folder hierarchy.`)}getHierarchyKeys(){return this.settings.frontmatterHierarchy.split(/[,\r\n]+/).map(t=>t.trim()).filter(Boolean)}getFolderSegmentsForFile(t,e){let n=this.app.metadataCache.getFileCache(t);if(!n?.frontmatter)return null;let r=[];for(let a of e){let s=this.getFrontmatterValueByKey(n.frontmatter,a),o=this.normalizeFrontmatterValue(s);if(o===null)break;r.push(this.sanitizeFolderName(o))}return r.length?r:null}getFrontmatterValueByKey(t,e){let n=e.trim().toLowerCase();for(let[r,a]of Object.entries(t))if(r.trim().toLowerCase()===n)return a}normalizeFrontmatterValue(t){if(t==null||typeof t=="object")return null;let e=String(t).trim();return e.length?e:null}sanitizeFolderName(t){return t.replace(/[<>:"/\\|?*]/g,"-").trim()}async ensureFolderExists(t){let e=t.replace(/\\/g,"/").split("/").filter(Boolean),n="";for(let r of e)n=n?`${n}/${r}`:r,this.app.vault.getAbstractFileByPath(n)||await this.app.vault.createFolder(n)}async getAvailableTargetPath(t,e){let n=t.replace(/\\/g,"/"),r=`${n}/${e}`;if(!this.app.vault.getAbstractFileByPath(r))return r;let a=e.lastIndexOf("."),s=a>=0?e.slice(0,a):e,o=a>=0?e.slice(a):"",c=1;for(;;){let u=`${n}/${s}-${c}${o}`;if(!this.app.vault.getAbstractFileByPath(u))return u;c+=1}}async loadSettings(){this.settings=Object.assign({},f,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "frontmatter-folder-organizer",
|
||||
"name": "Frontmatter Folder Organizer",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.0.0",
|
||||
"description": "Organize markdown files into folders based on frontmatter hierarchy.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "frontmatter-markdown-links",
|
||||
"name": "Frontmatter Markdown Links",
|
||||
"version": "2.6.34",
|
||||
"description": "Adds support for markdown links in frontmatter.",
|
||||
"author": "mnaoumov",
|
||||
"authorUrl": "https://github.com/mnaoumov/",
|
||||
"isDesktopOnly": false,
|
||||
"minAppVersion": "1.12.7",
|
||||
"fundingUrl": "https://www.buymeacoffee.com/mnaoumov"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.frontmatter-markdown-links.text-property-widget-component.metadata-property-value{gap:0}.frontmatter-markdown-links.text-property-widget-component.metadata-property-value>.metadata-property-value{--metadata-divider-width: 0}.frontmatter-markdown-links.text-property-widget-component.metadata-property-value>.metadata-property-value>.metadata-property-value:not(:first-of-type) .metadata-link,.frontmatter-markdown-links.text-property-widget-component.metadata-property-value>.metadata-property-value>.metadata-property-value:not(:first-of-type) .metadata-input-longtext{padding-left:0}.frontmatter-markdown-links.text-property-widget-component.metadata-property-value>.metadata-property-value>.metadata-property-value:not(:last-of-type) .metadata-link,.frontmatter-markdown-links.text-property-widget-component.metadata-property-value>.metadata-property-value>.metadata-property-value:not(:last-of-type) .metadata-input-longtext{padding-right:0}.frontmatter-markdown-links.text-property-widget-component.metadata-property-value .metadata-link-flair{display:none}.frontmatter-markdown-links .metadata-property-value,.frontmatter-markdown-links.multi-text-property-component{display:flex;flex:none;gap:0;white-space:pre}.bases-table-container:not(.mod-multiline) .frontmatter-markdown-links .metadata-input-longtext:not(:focus-within){white-space-collapse:preserve}
|
||||
@@ -8,69 +8,17 @@
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "d6fd830908a482d3",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "image",
|
||||
"state": {
|
||||
"file": "Images/Zorp, Unrecyclable.png"
|
||||
},
|
||||
"icon": "lucide-image",
|
||||
"title": "Zorp, Unrecyclable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "260e40ab6f70e60e",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "image",
|
||||
"state": {
|
||||
"file": "Agents Immortalized/Lifeblood/Horticulturist Oswald.png"
|
||||
},
|
||||
"icon": "lucide-image",
|
||||
"title": "Horticulturist Oswald"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "9aaa8a83f2165190",
|
||||
"id": "c90153d5f925b0d5",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Factions/Splintergleam.md",
|
||||
"file": "Decks/Big Energy.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
"source": true
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Splintergleam"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6870293355431c6b",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Factions/Singularity.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Singularity"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c17e1da0a76701dd",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Factions/Singularity.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Singularity"
|
||||
"title": "Big Energy"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -105,7 +53,7 @@
|
||||
"state": {
|
||||
"type": "search",
|
||||
"state": {
|
||||
"query": "The Ripper",
|
||||
"query": "",
|
||||
"matchingCase": false,
|
||||
"explainSearch": false,
|
||||
"collapseAll": false,
|
||||
@@ -130,7 +78,7 @@
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 331.5
|
||||
"width": 233.5
|
||||
},
|
||||
"right": {
|
||||
"id": "47f3bcd7aed212ab",
|
||||
@@ -235,55 +183,55 @@
|
||||
"bases:Create new base": false
|
||||
}
|
||||
},
|
||||
"active": "d6fd830908a482d3",
|
||||
"active": "c90153d5f925b0d5",
|
||||
"lastOpenFiles": [
|
||||
"Images/Zel, the First Diver.png",
|
||||
"Images/Affront to Nature.png",
|
||||
"Swashbuckling Diehard.md",
|
||||
"Decks/Big Energy.md",
|
||||
"_Decks.base",
|
||||
"Spell/[[Lifeblood]]/Bloom.md",
|
||||
"Timeline/[[Lifeblood]]/Deadly Fauna.md",
|
||||
"Images/Wolf.png",
|
||||
"Images/Seedling.png",
|
||||
"Images/Pocket Scout.png",
|
||||
"Images/Unlocked Potential.png",
|
||||
"Images/Throw into the Sun.png",
|
||||
"Images/Supernova.png",
|
||||
"Images/Sunshock.png",
|
||||
"Images/Soothing Glow.png",
|
||||
"Images/Radiant Channeling.png",
|
||||
"Token/[[Lifeblood]]/Seedling.md",
|
||||
"Token/[[Lifeblood]]/Wolf.md",
|
||||
"Token/[[Singularity]]/Pocket Scout.md",
|
||||
"Decks/Rewind Me.canvas",
|
||||
"Images",
|
||||
"Immortalized/[[Splintergleam]]/The Ripper.md",
|
||||
"Immortalized/[[Splintergleam]]/The Ripper.png.crdownload",
|
||||
"Agent/[[Splintergleam]]/Toothy Pugilist.md",
|
||||
"Immortalized/[[Silence]]/Alina Who Cuts the Strings.md",
|
||||
"Agent/[[Silence]]/Destiny Ripper.md",
|
||||
"Immortalized/[[Splintergleam]]/Sareh, Rebel Strategist.md",
|
||||
"Immortalized/[[Splintergleam]]/Scoot Sparkles.md",
|
||||
"Immortalized/[[Splintergleam]]/Scoot Sparkles.png.crdownload",
|
||||
"Agent/[[Phasetide]]/Divergence Assassin.md",
|
||||
"Immortalized/[[Lifeblood]]/Horticulturist Oswald.md",
|
||||
"Immortalized/[[Phasetide]]/Violent Inquisitioner.md",
|
||||
"Immortalized/[[Silence]]/A'kon, Starry Diviner.md",
|
||||
"Immortalized/[[Phasetide]]/Masa, Time-Lost.md",
|
||||
"Immortalized/[[Phasetide]]/Dedicated Missionary.md",
|
||||
"Immortalized/[[Phasetide]]/Violet Inquisitioner.md",
|
||||
"Immortalized/[[Phasetide]]/Zel, the First Diver.md",
|
||||
"Immortalized/[[Phasetide]]/The 'Stache.md",
|
||||
"Immortalized/[[Phasetide]]/Symphony of the Path.md",
|
||||
"Immortalized/[[Phasetide]]/Grand Judge Dhael.md",
|
||||
"Immortalized/[[Phasetide]]/Harker, Metal Reporter.md",
|
||||
"Immortalized/[[Phasetide]]/Khaela the Savior.md",
|
||||
"Immortalized/[[Phasetide]]/Pontifex Dhabu.md",
|
||||
"Agent/[[Lifeblood]]",
|
||||
"Agent/[[Singularity]]",
|
||||
"Agent/[[Phasetide]]",
|
||||
"Agent/[[Silence]]",
|
||||
"Agent/[[Sungrace]]",
|
||||
"Agent/[[Splintergleam]]",
|
||||
"Decks/Rewind Me.md",
|
||||
"Overpower.md",
|
||||
"_Keyword.base",
|
||||
"_Timeline.base",
|
||||
"Bronk the Calm.md",
|
||||
"Aggressive Recycling.md",
|
||||
"Agents.md",
|
||||
"Affront to Nature.png",
|
||||
"Aardvark Precinct Captain.png",
|
||||
"Aardvark Precinct Captain.md",
|
||||
"A'kon, Starry Diviner.md",
|
||||
"_Agents.base",
|
||||
"_Factions.base",
|
||||
"_Spells.base",
|
||||
"Xae, Dreamstrider.md",
|
||||
"Ylka, the Headliner.md",
|
||||
"Zealot of the Hunt.md",
|
||||
"Zel, the First Diver.md",
|
||||
"Ziv, the Adaptable.md",
|
||||
"Zorp, Unrecyclable.md",
|
||||
"Muffle.md",
|
||||
"Mr. E.md",
|
||||
"Nanobot Hive.md",
|
||||
"Nascent Clone.md",
|
||||
"E-Law, Boot Shepherd.md",
|
||||
"Efficient Scrapbot.md",
|
||||
"Egg Tender.md",
|
||||
"Enhanced Retriever.md",
|
||||
"Enlightened Refugee.md",
|
||||
"Enlightened Survivor.md",
|
||||
"Enthusiastic Bot-Poke.md",
|
||||
"Fervent Follower.png",
|
||||
"Fervent Mycologist.png",
|
||||
"Bearer of the Broth.png",
|
||||
"Battleharts.png",
|
||||
"Appeal to the Scrolls.png",
|
||||
"Zealot of the Hunt.png",
|
||||
"Dedicated Missionary.png",
|
||||
"Convergent Pack.png",
|
||||
"Master of Ceremonies.png.crdownload",
|
||||
"Master of Ceremonies_files/v833ccba57c9e4d2798f2e76cebdd09a11778172276447",
|
||||
"Master of Ceremonies_files/flux.min.js.download",
|
||||
"Master of Ceremonies_files/livewire.min.js.download",
|
||||
"_Agents.canvas"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 1
|
||||
attack: 2
|
||||
health: 2
|
||||
@@ -17,4 +17,3 @@ imageLink: "[[A'kon, Starry Diviner.png]]"
|
||||
|
||||
|
||||
![[A'kon, Starry Diviner.png]]
|
||||
|
||||
|
After Width: | Height: | Size: 157 KiB |
@@ -14,8 +14,7 @@ archetypes:
|
||||
- "[[Sprout]]"
|
||||
- "[[Shift]]"
|
||||
imageLink: "[[Aardvark Precinct Captain.png]]"
|
||||
ranking: A
|
||||
---
|
||||
I feel like it easy to always make this card sprout 5, so +5/+5 worth of stats for two man. Plus at that point, it will Immortalize as a +3/+3 and activate [[Deadly Fauna]].
|
||||
|
||||
|
||||
![[Aardvark Precinct Captain.png]]
|
||||
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 115 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 4
|
||||
attack: 3
|
||||
health: 2
|
||||
@@ -17,4 +17,3 @@ imageLink: "[[Alina Who Cuts the Strings.png]]"
|
||||
|
||||
|
||||
![[Alina Who Cuts the Strings.png]]
|
||||
|
||||
|
After Width: | Height: | Size: 213 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 4
|
||||
attack: 3
|
||||
health: 5
|
||||
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 199 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 5
|
||||
attack: 6
|
||||
health: 6
|
||||
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 164 KiB |
@@ -0,0 +1,19 @@
|
||||
---
|
||||
category: Agent
|
||||
cost: 1
|
||||
attack: 1
|
||||
health: 2
|
||||
description: "[[Activate]]: The next ally that enters play this round [[Flourish|Flourishes]]. The first time each round another ally [[Flourish|Flourishes]], I [[Flourish]]."
|
||||
immortalizeWhen: N/A
|
||||
immortalizeTo:
|
||||
- N/A
|
||||
immortalizeFrom: "[[Egg Tender]]"
|
||||
faction: "[[Lifeblood]]"
|
||||
set: "[[Core Set]]"
|
||||
archetypes:
|
||||
- "[[Flourish]]"
|
||||
imageLink: "[[Arra, Saurian Broodmother.png]]"
|
||||
---
|
||||
|
||||
|
||||
![[Arra, Saurian Broodmother.png]]
|
||||
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 220 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 2
|
||||
attack: 3
|
||||
health: 2
|
||||
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 142 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 2
|
||||
attack: 0
|
||||
health: 1
|
||||
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 138 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 4
|
||||
attack: 3
|
||||
health: 4
|
||||
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 170 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 7
|
||||
attack: 6
|
||||
health: 10
|
||||
|
After Width: | Height: | Size: 146 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 1
|
||||
attack: 3
|
||||
health: 5
|
||||
|
After Width: | Height: | Size: 162 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 2
|
||||
attack: 3
|
||||
health: 3
|
||||
|
After Width: | Height: | Size: 112 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 2
|
||||
attack: 4
|
||||
health: 4
|
||||
|
After Width: | Height: | Size: 137 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 2
|
||||
attack: 2
|
||||
health: 1
|
||||
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 123 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
category: Immortalized
|
||||
category: Agent
|
||||
cost: 1
|
||||
attack: 2
|
||||
health: 3
|
||||
|
After Width: | Height: | Size: 142 KiB |