Vibe documents

This commit is contained in:
6d486f49
2026-06-23 13:30:43 -04:00
parent 6eaf7b0980
commit 89bd1ca1f9
92 changed files with 69503 additions and 67 deletions
+174 -9
View File
@@ -7,6 +7,38 @@ public static class CardDatabase
{
public static readonly System.Collections.Generic.List<CardData> Cards =
[
new()
{
Name = "Core Charges",
Category = "Currency",
Description = "Earnable currency, can be used to buy packs.",
Archetypes = [
],
},
new()
{
Name = "Crystals",
Category = "Currency",
Description = "Premium currency, can be used to buy packs and cosmetics.",
Archetypes = [
],
},
new()
{
Name = "Draft Ticket",
Category = "Currency",
Description = "Spend to participate in a draft.",
Archetypes = [
],
},
new()
{
Name = "Flux",
Category = "Currency",
Description = "Can be used to craft cards via wildcards.",
Archetypes = [
],
},
new()
{
Name = "Bleed",
@@ -298,6 +330,69 @@ public static class CardDatabase
],
},
new()
{
Name = "Daily Wins",
Category = "Quest",
Archetypes = [
],
},
new()
{
Name = "Deal 200 Damage",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Deal 30 Damage with Overpower Agents",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Deal 50 Damage with Specific Faction",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Deplete 10 Agents",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Erase 7 Cards",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Overflow 10 Times",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Weekly Wins",
Category = "Quest",
Description = "",
Archetypes = [
],
},
new()
{
Name = "Agents",
Category = "Redirect",
@@ -335,6 +430,22 @@ public static class CardDatabase
],
},
new()
{
Name = "Fast",
Category = "Rule",
Description = "This spell can be reacted to.",
Archetypes = [
],
},
new()
{
Name = "Immediate",
Category = "Rule",
Description = "This spell cannot be reacted to.",
Archetypes = [
],
},
new()
{
Name = "Round End",
Category = "Rule",
@@ -343,6 +454,14 @@ public static class CardDatabase
],
},
new()
{
Name = "Slow",
Category = "Rule",
Description = "This spell cannot be used as a reaction to something on the Chain.",
Archetypes = [
],
},
new()
{
Name = "Core Set",
Category = "Set",
@@ -351,25 +470,71 @@ public static class CardDatabase
},
new()
{
Name = "Fast",
Category = "Speed",
Description = "This spell can be reacted to.",
Name = "Card Backs",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Immediate",
Category = "Speed",
Description = "This spell cannot be reacted to.",
Name = "Common",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Slow",
Category = "Speed",
Description = "This spell cannot be used as a reaction to something on the Chain.",
Name = "Core Set Packs x1",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Core Set Packs x10",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Core Set Packs x5",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Core Set Packs x50",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Divergent",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Lost",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Rare",
Category = "Store",
Archetypes = [
],
},
new()
{
Name = "Text Emote",
Category = "Store",
Archetypes = [
],
},
+862
View File
@@ -0,0 +1,862 @@
// <auto-generated/>
#nullable enable
namespace Chrono.Model;
public static class DocDatabase
{
public static readonly System.Collections.Generic.List<DocData> Docs =
[
new()
{
Title = "Core Charges",
Category = "Currency",
Content = "",
Frontmatter = new()
{
{ "category", "Currency" },
{ "description", "Earnable currency, can be used to buy packs." },
},
},
new()
{
Title = "Crystals",
Category = "Currency",
Content = "",
Frontmatter = new()
{
{ "category", "Currency" },
{ "description", "Premium currency, can be used to buy packs and cosmetics." },
},
},
new()
{
Title = "Draft Ticket",
Category = "Currency",
Content = "",
Frontmatter = new()
{
{ "category", "Currency" },
{ "description", "Spend to participate in a draft." },
},
},
new()
{
Title = "Flux",
Category = "Currency",
Content = "",
Frontmatter = new()
{
{ "category", "Currency" },
{ "description", "Can be used to craft cards via wildcards." },
},
},
new()
{
Title = "Bleed",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "This Agent takes X damage at Round End." },
},
},
new()
{
Title = "Decay",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "Grant this Agent -1/-1." },
},
},
new()
{
Title = "Delay",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "This Agent always Strikes after its opponent in combat. Replaces Blitz." },
},
},
new()
{
Title = "Disarm",
Category = "Debuff",
Content = "Reduce attack to zero.",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "Set this Agent's strength to 0 until Round End." },
},
},
new()
{
Title = "Erase",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "Remove this card from the game." },
},
},
new()
{
Title = "Exposed",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "This Agent may be chosen as a blocker by enemy Agents when confirming their attack." },
},
},
new()
{
Title = "Mute",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "Remove all text as well as any Strength/Durability changes from this Agent. Muted Agents have no name." },
},
},
new()
{
Title = "Temporary",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "This Agent is destroyed at Round End or when it Strikes." },
},
},
new()
{
Title = "Transient",
Category = "Debuff",
Content = "",
Frontmatter = new()
{
{ "category", "Debuff" },
{ "description", "This card in hand is Discarded at Round End." },
},
},
new()
{
Title = "Lifeblood",
Category = "Faction",
Content = "Concepts:\n- Wide Boards\n- Overpower",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Phasetide",
Category = "Faction",
Content = "Concepts:\n- Healing\n-",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Silence",
Category = "Faction",
Content = "Concepts:\n- Activatable Agents\n-",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Singularity",
Category = "Faction",
Content = "Concepts:\n- Discard",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Splintergleam",
Category = "Faction",
Content = "Concepts:\n- Self Damage\n- Bleed\n-",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Sungrace",
Category = "Faction",
Content = "Concepts:\n- Ramp\n- Using and Replenishing Reserved Energy\n-",
Frontmatter = new()
{
{ "category", "Faction" },
},
},
new()
{
Title = "Blitz",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "When attack, this Agent strikes before its blocker. Replaces [[Delay]]." },
},
},
new()
{
Title = "Breakdown",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "When your Core has X or less Durability remaining this effect triggers." },
},
},
new()
{
Title = "Cleave",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "When Striking a blocker, this Agent also Strikes Agents to the left and right of its blocker at the same time." },
},
},
new()
{
Title = "Confront",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "This Agent may choose an enemy Agent to block it when confirming their attack." },
},
},
new()
{
Title = "Draw",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Place the card from the top of your deck into your hand." },
},
},
new()
{
Title = "Evasive",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "This Agent may only be blocker by other Evasive Agents." },
},
},
new()
{
Title = "Fervor",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "When this Agent survives damage, grant it +1/+1." },
},
},
new()
{
Title = "Flourish",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Grant this Agent +1/+1." },
},
},
new()
{
Title = "Heal",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Increase the Durability of the Agent or Core being healed by this amount. Agents being healed cannot exceed their maximum Durability. Cores have no maximum Durability and can be healed any amount." },
},
},
new()
{
Title = "Overpower",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Excess damage beyond the Durability of this Agent's blocker is dealt directly to the enemy core." },
},
},
new()
{
Title = "Phase",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "A [[Phased]] [[Agent]] is treated as removed from play for the round, though it still occupies its board-space. At the start of the next round this unit Phases in and is in the exact same state as it Phased out. Phasing back in does not trigger Enter effects." },
},
},
new()
{
Title = "Phased",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Triggered when this [[Agent]] has been affected by [[Phase]]." },
},
},
new()
{
Title = "Refresh",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Remove the Depleted status." },
},
},
new()
{
Title = "Rejuvenate",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "At Round Start heal this Agent to full Durability." },
},
},
new()
{
Title = "Revive",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Return from the Graveyard to play." },
},
},
new()
{
Title = "Rewind",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Return this [[Agent]] to its owner's hand." },
},
},
new()
{
Title = "Rewound",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Triggered when this [[Agent]] has been affected by [[Rewind]]." },
},
},
new()
{
Title = "Shift",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Timeline is changed to the indicated Timeline." },
},
},
new()
{
Title = "Siphon",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "When an Agent strikes or an Action, Effect, or Timeline with the Siphon icon deals damage, heal your Core that amount." },
},
},
new()
{
Title = "Sprout",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Create X 1/1 Seedling Tokens. If your board is full, grant your weakest Seedling +1|+1 instead." },
},
},
new()
{
Title = "Surge",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "Gain an attack token if you dont already have one." },
},
},
new()
{
Title = "Transient",
Category = "Keyword",
Content = "",
Frontmatter = new()
{
{ "category", "Keyword" },
{ "description", "This card in hand is [[Discarded]] at [[Round End]]." },
},
},
new()
{
Title = "Daily Wins",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
},
},
new()
{
Title = "Deal 200 Damage",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Deal 30 Damage with Overpower Agents",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Deal 50 Damage with Specific Faction",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Deplete 10 Agents",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Erase 7 Cards",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Overflow 10 Times",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
{ "codeCharges", "" },
{ "exp", "" },
},
},
new()
{
Title = "Weekly Wins",
Category = "Quest",
Content = "",
Frontmatter = new()
{
{ "category", "Quest" },
{ "description", "" },
},
},
new()
{
Title = "Agents",
Category = "Redirect",
Content = "",
Frontmatter = new()
{
{ "category", "Redirect" },
{ "see", "\"[[Agent]]\"" },
},
},
new()
{
Title = "Cores",
Category = "Redirect",
Content = "",
Frontmatter = new()
{
{ "category", "Redirect" },
{ "see", "\"[[Core]]\"" },
},
},
new()
{
Title = "Chain",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "Effects are stacked until they are able to be resolved. This enables [[Immediate]] and [[Fast]] reactions to be added onto the stack for reactive gameplay." },
},
},
new()
{
Title = "Core",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
},
},
new()
{
Title = "Discarded",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "Remove this card from your hand." },
},
},
new()
{
Title = "Fast",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "This spell can be reacted to." },
},
},
new()
{
Title = "Immediate",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "This spell cannot be reacted to." },
},
},
new()
{
Title = "Round End",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "The end state of the round. Triggered when both players end there turns without action." },
},
},
new()
{
Title = "Slow",
Category = "Rule",
Content = "",
Frontmatter = new()
{
{ "category", "Rule" },
{ "description", "This spell cannot be used as a reaction to something on the [[Chain]]." },
},
},
new()
{
Title = "Core Set",
Category = "Set",
Content = "",
Frontmatter = new()
{
{ "category", "Set" },
},
},
new()
{
Title = "Card Backs",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "" },
{ "crytalPrice", "\"1450\"" },
},
},
new()
{
Title = "Common",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "25" },
},
},
new()
{
Title = "Core Set Packs x1",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "crytalPrice", "\"450\"" },
{ "coreChargesPrice", "\"120\"" },
{ "essencePrice", "" },
{ "pricePerPack", "\"450\"" },
},
},
new()
{
Title = "Core Set Packs x10",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "crytalPrice", "\"3900\"" },
{ "coreChargesPrice", "\"1200\"" },
{ "essencePrice", "" },
{ "pricePerPack", "\"390\"" },
},
},
new()
{
Title = "Core Set Packs x5",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "crytalPrice", "\"2100\"" },
{ "coreChargesPrice", "\"600\"" },
{ "essencePrice", "" },
{ "pricePerPack", "\"420\"" },
},
},
new()
{
Title = "Core Set Packs x50",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "" },
{ "crytalPrice", "\"18000\"" },
{ "coreChargesPrice", "\"6000\"" },
{ "pricePerPack", "\"360\"" },
},
},
new()
{
Title = "Divergent",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "200" },
},
},
new()
{
Title = "Lost",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "currency", "essence" },
{ "essencePrice", "400" },
},
},
new()
{
Title = "Rare",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "75" },
},
},
new()
{
Title = "Text Emote",
Category = "Store",
Content = "",
Frontmatter = new()
{
{ "category", "Store" },
{ "essencePrice", "" },
{ "crytalPrice", "\"450\"" },
},
},
new()
{
Title = "Deadly Fauna",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "All Agents have [[Overpower]]." },
{ "faction", "\"[[Lifeblood]]\"" },
},
},
new()
{
Title = "The One True Timeline",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "\"Round Start: Heal all [[Agents]] and [[Cores]] 1.\"" },
{ "faction", "\"[[Phasetide]]\"" },
},
},
new()
{
Title = "Voiceless Sky",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "If a player has exactly one Agent, it has +3/+3." },
{ "faction", "\"[[Silence]]\"" },
},
},
new()
{
Title = "Erudite Beacon",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "\"When you Shift here, draw 1. Round Start: Players draw 1.\"" },
{ "faction", "\"[[Singularity]]\"" },
},
},
new()
{
Title = "Torment",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "All Agents and Cores have Bleed 1." },
{ "faction", "\"[[Splintergleam]]\"" },
},
},
new()
{
Title = "Volcanic Rivers",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "When you Shift here, deal 1 to both Cores." },
{ "faction", "\"[[Splintergleam]]\"" },
},
},
new()
{
Title = "Star Siphon",
Category = "Timeline",
Content = "",
Frontmatter = new()
{
{ "category", "Timeline" },
{ "description", "\" Round End: Refill all Energy Reserves.\"" },
{ "faction", "\"[[Sungrace]]\"" },
},
}
];
}
+5
View File
@@ -33,6 +33,11 @@
<i class="bi bi-journal-text nav-icon"></i> Decks
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="docs">
<i class="bi bi-book-fill nav-icon"></i> Docs
</NavLink>
</div>
</nav>
</div>
+237
View File
@@ -0,0 +1,237 @@
@namespace Shared.Pages
@page "/docs"
<PageTitle>Docs</PageTitle>
<div class="docs-page">
<div class="docs-header">
<h1>Documentation</h1>
<p class="text-muted">Rules, keywords, factions, and more.</p>
</div>
<div class="docs-container">
<aside class="docs-sidebar">
<div class="search-box">
<i class="bi bi-search"></i>
<input type="text" placeholder="Search docs..." @bind="searchQuery" @bind:event="oninput" />
</div>
<div class="category-list">
<div class="category-item @(selectedCategory == null ? "active" : "")" @onclick="() => SelectCategory(null)">
All Pages
</div>
@foreach (var category in categories)
{
var cat = category;
<div class="category-item @(selectedCategory == cat ? "active" : "")" @onclick="() => SelectCategory(cat)">
@cat
</div>
}
</div>
</aside>
<main class="docs-content">
@if (selectedDoc == null)
{
<div class="doc-list">
@foreach (var doc in filteredDocs)
{
<div class="doc-card" @onclick="() => SelectDoc(doc)">
<div class="doc-card-category">@doc.Category</div>
<h3 class="doc-card-title">@doc.Title</h3>
@{
var desc = GetDescription(doc);
}
@if (desc != null)
{
<p class="doc-card-desc">@desc</p>
}
</div>
}
@if (!filteredDocs.Any())
{
<div class="empty-state">
<i class="bi bi-search"></i>
<p>No documentation found matching your search.</p>
</div>
}
</div>
}
else
{
<article class="doc-article">
<button class="btn-back" @onclick="() => SelectDoc(null)">
<i class="bi bi-arrow-left"></i> Back to list
</button>
<header class="doc-header">
<div class="doc-meta">@selectedDoc.Category</div>
<h1>@selectedDoc.Title</h1>
@{
var desc = GetDescription(selectedDoc);
}
@if (desc != null)
{
<p class="doc-description">@desc</p>
}
</header>
@if (!string.IsNullOrWhiteSpace(selectedDoc.Content))
{
<div class="doc-body">
@((MarkupString)FormatContent(selectedDoc.Content))
</div>
}
@if (HasExtraFrontmatter(selectedDoc))
{
<div class="doc-extra">
<h3>Details</h3>
<div class="doc-frontmatter">
@foreach (var kvp in selectedDoc.Frontmatter.Where(kvp => kvp.Key != "category" && kvp.Key != "description"))
{
<div class="fm-item">
<span class="fm-key">@kvp.Key</span>
<span class="fm-value">@kvp.Value</span>
</div>
}
</div>
</div>
}
</article>
}
</main>
</div>
</div>
@code {
private string searchQuery = "";
private string? selectedCategory;
private DocData? selectedDoc;
private List<string> categories = [];
private List<DocData> allDocs = [];
protected override void OnInitialized()
{
allDocs = DocDatabase.Docs.OrderBy(d => d.Category).ThenBy(d => d.Title).ToList();
categories = allDocs.Select(d => d.Category).Distinct().OrderBy(c => c).ToList();
}
private IEnumerable<DocData> filteredDocs => allDocs
.Where(d => (selectedCategory == null || d.Category == selectedCategory) &&
(string.IsNullOrWhiteSpace(searchQuery) ||
d.Title.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) ||
(GetDescription(d)?.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) ?? false) ||
d.Content.Contains(searchQuery, StringComparison.OrdinalIgnoreCase)));
private void SelectCategory(string? category)
{
selectedCategory = category;
selectedDoc = null;
}
private void SelectDoc(DocData? doc)
{
selectedDoc = doc;
}
private static string? GetDescription(DocData doc)
{
return doc.Frontmatter.TryGetValue("description", out var desc) && !string.IsNullOrWhiteSpace(desc) ? desc : null;
}
private static bool HasExtraFrontmatter(DocData doc)
{
var count = doc.Frontmatter.Count;
if (count == 0) return false;
if (count == 1 && doc.Frontmatter.ContainsKey("category")) return false;
if (count == 2 && doc.Frontmatter.ContainsKey("category") && doc.Frontmatter.ContainsKey("description")) return false;
return doc.Frontmatter.Any(kvp => kvp.Key != "category" && kvp.Key != "description");
}
private string FormatContent(string content)
{
if (string.IsNullOrWhiteSpace(content)) return "";
var lines = content.Replace("\r\n", "\n").Split('\n');
var html = new System.Text.StringBuilder();
var inList = false;
foreach (var rawLine in lines)
{
var line = rawLine.TrimEnd();
if (string.IsNullOrWhiteSpace(line))
{
if (inList) { html.AppendLine("</ul>"); inList = false; }
continue;
}
if (line.StartsWith("- "))
{
var item = line[2..].Trim();
if (item.Length > 0)
{
if (!inList) { html.AppendLine("<ul class=\"doc-list-items\">"); inList = true; }
html.Append("<li>");
html.Append(EncodeInline(item));
html.AppendLine("</li>");
}
continue;
}
if (inList) { html.AppendLine("</ul>"); inList = false; }
if (line.EndsWith(":") || line.Contains(":\n"))
{
var trimmed = line.TrimEnd(':');
if (trimmed.Length > 0)
{
html.Append("<p class=\"doc-label\">");
html.Append(System.Web.HttpUtility.HtmlEncode(trimmed));
html.AppendLine("</p>");
}
}
else
{
html.Append("<p>");
html.Append(EncodeInline(line));
html.AppendLine("</p>");
}
}
if (inList) html.AppendLine("</ul>");
return html.ToString();
}
private string EncodeInline(string text)
{
var result = new System.Text.StringBuilder();
var remaining = text;
while (remaining.Length > 0)
{
var boldIdx = remaining.IndexOf("**", StringComparison.Ordinal);
if (boldIdx >= 0)
{
var endBold = remaining.IndexOf("**", boldIdx + 2, StringComparison.Ordinal);
if (endBold >= 0)
{
result.Append(System.Web.HttpUtility.HtmlEncode(remaining[..boldIdx]));
result.Append("<strong>");
result.Append(System.Web.HttpUtility.HtmlEncode(remaining[(boldIdx + 2)..endBold]));
result.Append("</strong>");
remaining = remaining[(endBold + 2)..];
continue;
}
}
result.Append(System.Web.HttpUtility.HtmlEncode(remaining));
break;
}
return result.ToString();
}
}
+325
View File
@@ -0,0 +1,325 @@
.docs-page {
padding: 1rem 2rem 3rem;
max-width: 1200px;
margin: 0 auto;
}
.docs-header {
margin-bottom: 2rem;
}
.docs-header h1 {
margin-bottom: 0.25rem;
}
.docs-header p {
font-size: 0.9rem;
margin: 0;
}
.docs-container {
display: grid;
grid-template-columns: 220px 1fr;
gap: 2.5rem;
}
.docs-sidebar {
position: sticky;
top: 2rem;
align-self: start;
}
.search-box {
position: relative;
margin-bottom: 1.5rem;
}
.search-box i {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
font-size: 0.85rem;
}
.search-box input {
width: 100%;
padding: 0.55rem 0.75rem 0.55rem 2.25rem;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text-primary);
font-size: 0.88rem;
font-family: inherit;
}
.search-box input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-glow);
}
.category-list {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.category-item {
padding: 0.45rem 0.75rem;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.88rem;
color: var(--text-secondary);
transition: all var(--transition);
}
.category-item:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.category-item.active {
background: rgba(108, 99, 255, 0.12);
color: var(--accent);
font-weight: 600;
}
.doc-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.doc-card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.1rem 1.4rem;
cursor: pointer;
transition: all var(--transition);
}
.doc-card:hover {
border-color: var(--accent);
box-shadow: 0 0 15px var(--accent-glow);
transform: translateY(-1px);
}
.doc-card-category {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
font-weight: 600;
margin-bottom: 0.3rem;
}
.doc-card-title {
margin: 0 0 0.35rem;
font-size: 1.15rem;
font-weight: 600;
}
.doc-card-desc {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.5;
margin: 0;
}
.doc-article {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 2rem 2.5rem;
}
.btn-back {
background: none;
border: none;
color: var(--text-muted);
font-size: 0.88rem;
padding: 0;
margin-bottom: 1.5rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.4rem;
font-family: inherit;
transition: color var(--transition);
}
.btn-back:hover {
color: var(--text-primary);
}
.doc-header {
margin-bottom: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.doc-meta {
font-size: 0.8rem;
color: var(--accent);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.4rem;
}
.doc-header h1 {
margin: 0 0 0.75rem;
font-size: 1.8rem;
font-weight: 700;
}
.doc-description {
font-size: 1.05rem;
line-height: 1.6;
color: var(--text-secondary);
margin: 0;
}
.doc-body {
line-height: 1.7;
font-size: 1rem;
color: var(--text-primary);
margin-bottom: 1.5rem;
}
.doc-body p {
margin: 0 0 0.6rem;
}
.doc-body p:last-child {
margin-bottom: 0;
}
.doc-label {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.4rem;
}
.doc-list-items {
margin: 0 0 0.8rem;
padding-left: 1.5rem;
list-style: none;
}
.doc-list-items li {
position: relative;
padding: 0.2rem 0;
color: var(--text-secondary);
font-size: 0.95rem;
}
.doc-list-items li::before {
content: "▸";
position: absolute;
left: -1.2rem;
color: var(--accent);
font-size: 0.75rem;
}
.doc-extra {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
}
.doc-extra h3 {
font-size: 0.9rem;
font-weight: 600;
margin: 0 0 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.doc-frontmatter {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.4rem 1rem;
padding: 0.75rem 1rem;
background: var(--bg-elevated);
border-radius: var(--radius);
border: 1px solid var(--border);
}
.fm-item {
display: contents;
}
.fm-key {
color: var(--text-muted);
font-weight: 500;
font-size: 0.85rem;
white-space: nowrap;
}
.fm-value {
color: var(--text-secondary);
font-size: 0.85rem;
}
.doc-link-btn {
background: none;
border: none;
padding: 0;
font-family: inherit;
font-size: inherit;
font-weight: 600;
color: var(--accent);
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-color: rgba(108, 99, 255, 0.3);
transition: color var(--transition), text-decoration-color var(--transition);
}
.doc-link-btn:hover {
color: #7b73ff;
text-decoration-color: #7b73ff;
}
.empty-state {
text-align: center;
padding: 4rem 1rem;
color: var(--text-muted);
}
.empty-state i {
font-size: 3rem;
display: block;
margin-bottom: 1rem;
}
@media (max-width: 900px) {
.docs-container {
grid-template-columns: 1fr;
}
.docs-sidebar {
position: static;
}
.category-list {
flex-direction: row;
overflow-x: auto;
gap: 0.4rem;
padding-bottom: 0.5rem;
}
.category-item {
white-space: nowrap;
flex-shrink: 0;
}
.doc-article {
padding: 1.25rem;
}
}
+1
View File
@@ -2,6 +2,7 @@
@using System.Net.Http.Json
@using Chrono.Model
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization