Vibe Google Analytics
This commit is contained in:
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
3.0
|
||||
@@ -60,7 +60,7 @@ foreach (var file in mdFiles)
|
||||
ImmortalizeWhen = NullIfNa(StripWikiLinks(yaml.GetValueOrDefault("immortalizeWhen"))),
|
||||
ImageFile = imageFile,
|
||||
Artist = yaml.GetValueOrDefault("artist"),
|
||||
Rarity = yaml.GetValueOrDefault("rarity"),
|
||||
Rarity = yaml.GetValueOrDefault("rarity")
|
||||
};
|
||||
|
||||
cards.Add(card);
|
||||
|
||||
@@ -31,9 +31,8 @@
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
&[open] {
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
private int currentCount;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that
|
||||
occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
|
||||
environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@@ -30,7 +32,9 @@
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
@using Shared.Pages
|
||||
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }" NotFoundPage="typeof(Pages.NotFound)">
|
||||
@using NotFound = Cloud.Components.Pages.NotFound
|
||||
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }"
|
||||
NotFoundPage="typeof(NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -11,7 +11,7 @@ var app = builder.Build();
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
app.UseExceptionHandler("/Error", true);
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -224,6 +224,7 @@ h6, h5, h4, h3, h2, h1 {
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
@@ -233,6 +234,7 @@ h1 {
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
@@ -242,6 +244,7 @@ h2 {
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
@@ -251,6 +254,7 @@ h3 {
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
@@ -351,6 +355,7 @@ a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
@@ -375,6 +380,7 @@ pre {
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
@@ -386,6 +392,7 @@ code {
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -397,6 +404,7 @@ kbd {
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
@@ -474,6 +482,7 @@ select {
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -488,6 +497,7 @@ button,
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
@@ -519,11 +529,13 @@ legend {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@ h6, h5, h4, h3, h2, h1 {
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
@@ -233,6 +234,7 @@ h1 {
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
@@ -242,6 +244,7 @@ h2 {
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
@@ -251,6 +254,7 @@ h3 {
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
@@ -351,6 +355,7 @@ a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
@@ -375,6 +380,7 @@ pre {
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
@@ -386,6 +392,7 @@ code {
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
@@ -397,6 +404,7 @@ kbd {
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
@@ -474,6 +482,7 @@ select {
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -488,6 +497,7 @@ button,
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
@@ -519,11 +529,13 @@ legend {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
@@ -553,6 +565,7 @@ legend + * {
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
@@ -591,4 +604,5 @@ progress {
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+204
-8
File diff suppressed because it is too large
Load Diff
+194
-5
File diff suppressed because it is too large
Load Diff
+196
-6
File diff suppressed because it is too large
Load Diff
@@ -5,5 +5,5 @@ public class DocData
|
||||
public string Title { get; init; } = "";
|
||||
public string Category { get; init; } = "";
|
||||
public string Content { get; init; } = "";
|
||||
public System.Collections.Generic.Dictionary<string, string> Frontmatter { get; init; } = [];
|
||||
public Dictionary<string, string> Frontmatter { get; init; } = [];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Shared.Pages.Home).Assembly }">
|
||||
<Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Server;
|
||||
using Server.Components;
|
||||
using Shared.Pages;
|
||||
using Shared.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -7,7 +10,7 @@ builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
builder.Services.AddTelerikBlazor();
|
||||
builder.Services.AddSingleton<Shared.Services.CardRenderingService>();
|
||||
builder.Services.AddSingleton<CardRenderingService>();
|
||||
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||
@@ -32,17 +35,14 @@ using (var scope = app.Services.CreateScope())
|
||||
}
|
||||
}
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
}
|
||||
if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/Error");
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapRazorComponents<Server.Components.App>()
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode()
|
||||
.AddAdditionalAssemblies(typeof(Shared.Pages.Home).Assembly);
|
||||
.AddAdditionalAssemblies(typeof(Home).Assembly);
|
||||
|
||||
app.Run();
|
||||
@@ -257,9 +257,6 @@ a, .btn-link {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.inline-card-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@namespace Shared.Components
|
||||
@using Chrono.Model
|
||||
|
||||
@if (Card != null)
|
||||
{
|
||||
@@ -105,7 +104,8 @@
|
||||
var target = LookupCard(targetName);
|
||||
if (target != null)
|
||||
{
|
||||
<button class="inline-card-btn" @onclick="() => SelectCard(target)">@targetName</button>
|
||||
<button class="inline-card-btn"
|
||||
@onclick="() => SelectCard(target)">@targetName</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,7 +125,8 @@
|
||||
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
||||
if (fromCard != null)
|
||||
{
|
||||
<button class="inline-card-btn" @onclick="() => SelectCard(fromCard)">@Card.ImmortalizeFrom</button>
|
||||
<button class="inline-card-btn"
|
||||
@onclick="() => SelectCard(fromCard)">@Card.ImmortalizeFrom</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -148,9 +149,15 @@
|
||||
|
||||
private bool HasImage => Card?.ImageFile is { Length: > 0 } && Card.ImageFile != "placeholder.png";
|
||||
|
||||
private async Task Close() => await OnClose.InvokeAsync();
|
||||
private async Task Close()
|
||||
{
|
||||
await OnClose.InvokeAsync();
|
||||
}
|
||||
|
||||
private async Task SelectCard(CardData card) => await OnSelectCard.InvokeAsync(card);
|
||||
private async Task SelectCard(CardData card)
|
||||
{
|
||||
await OnSelectCard.InvokeAsync(card);
|
||||
}
|
||||
|
||||
private RenderFragment RenderCardDescription(string description)
|
||||
{
|
||||
@@ -163,4 +170,5 @@
|
||||
return CardDatabase.Cards.FirstOrDefault(c =>
|
||||
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card-detail {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@namespace Shared.Components
|
||||
@using Chrono.Model
|
||||
|
||||
<div class="deck-card-row">
|
||||
@foreach (var group in Cards.GroupBy(x => x))
|
||||
@@ -29,10 +28,13 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public List<string> Cards { get; set; } = [];
|
||||
[Parameter][EditorRequired] public List<string> Cards { get; set; } = [];
|
||||
[Parameter] public EventCallback<CardData?> OnSelectCard { get; set; }
|
||||
|
||||
private async Task SelectCard(CardData? card) => await OnSelectCard.InvokeAsync(card);
|
||||
private async Task SelectCard(CardData? card)
|
||||
{
|
||||
await OnSelectCard.InvokeAsync(card);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, CardData> CardLookup = CardDatabase.Cards
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.Name))
|
||||
@@ -43,4 +45,5 @@
|
||||
{
|
||||
return CardLookup.GetValueOrDefault(cardName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="mana-curve">
|
||||
@foreach (var entry in Distribution)
|
||||
{
|
||||
var height = MaxCount > 0 ? (entry.Count * 100 / MaxCount) : 0;
|
||||
var height = MaxCount > 0 ? entry.Count * 100 / MaxCount : 0;
|
||||
<div class="mana-bar-container">
|
||||
<div class="mana-bar-label">@entry.Count</div>
|
||||
<div class="mana-bar" style="height: @($"{Math.Max(height, 5)}%")"></div>
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public List<(int Cost, int Count)> Distribution { get; set; } = [];
|
||||
[Parameter][EditorRequired] public List<(int Cost, int Count)> Distribution { get; set; } = [];
|
||||
[Parameter] public int MaxCostLabel { get; set; } = 10;
|
||||
|
||||
private int MaxCount => Distribution.Count > 0 ? Distribution.Max(x => x.Count) : 0;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
@implements IDisposable
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject AnalyticsService AnalyticsService
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.LocationChanged += OnLocationChanged;
|
||||
}
|
||||
|
||||
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
await TrackPageView(e.Location);
|
||||
}
|
||||
|
||||
private async Task TrackPageView(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var relativePath = NavigationManager.ToBaseRelativePath(url);
|
||||
if (!relativePath.StartsWith("/"))
|
||||
{
|
||||
relativePath = "/" + relativePath;
|
||||
}
|
||||
|
||||
await AnalyticsService.TrackPageView(relativePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during tracking
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Lifeblood",
|
||||
Category = "Faction",
|
||||
Content = "Concepts:\r\n- Wide Boards\r\n- Overpower",
|
||||
Content = "Concepts:\r\n\r\n- Wide Boards\r\n- Overpower",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "category", "Faction" },
|
||||
@@ -164,7 +164,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Phasetide",
|
||||
Category = "Faction",
|
||||
Content = "Concepts:\r\n- Healing\r\n-",
|
||||
Content = "Concepts:\r\n\r\n- Healing\r\n-",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "category", "Faction" },
|
||||
@@ -174,7 +174,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Silence",
|
||||
Category = "Faction",
|
||||
Content = "Concepts:\r\n- Activatable Agents\r\n-",
|
||||
Content = "Concepts:\r\n\r\n- Activatable Agents\r\n-",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "category", "Faction" },
|
||||
@@ -184,7 +184,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Singularity",
|
||||
Category = "Faction",
|
||||
Content = "Concepts:\r\n- Discard",
|
||||
Content = "Concepts:\r\n\r\n- Discard",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "category", "Faction" },
|
||||
@@ -204,7 +204,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Sungrace",
|
||||
Category = "Faction",
|
||||
Content = "# Energy Reserves\r\n\r\nCare about always having banked energy. Spells can refund themselves when using [[Energy Reserves]], or get stronger is you [[Overflow]] your energy reserve.\r\n\r\nConcepts:\r\n- Ramp\r\n- Using and Replenishing Reserved Energy\r\n-",
|
||||
Content = "# Energy Reserves\r\n\r\nCare about always having banked energy. Spells can refund themselves when using [[Energy Reserves]], or get stronger is\r\nyou [[Overflow]] your energy reserve.\r\n\r\nConcepts:\r\n\r\n- Ramp\r\n- Using and Replenishing Reserved Energy\r\n-",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "category", "Faction" },
|
||||
@@ -479,7 +479,7 @@ public static class DocDatabase
|
||||
{
|
||||
Title = "Onboarding Track",
|
||||
Category = "Pass",
|
||||
Content = "| Tier | Reward |\r\n| ---- | -------------------------------- |\r\n| 1 | Sprouts Record |\r\n| 2 | 120 Core Charges |\r\n| 3 | Burn Precon |\r\n| 4 | 120 Core Charges, 25 Flux |\r\n| 5 | Profile Pictures |\r\n| 6 | Emotes |\r\n| 7 | 120 Core Charges |\r\n| 8 | Profile Pictures |\r\n| 9 | 60 Core Charges, 50 Flux |\r\n| 10 | 240 Core Charges, 1 Draft Ticket |\r\n\r\n| Core Charges | Flux |\r\n| ------------ | ---- |\r\n| 660 | 75 |",
|
||||
Content = "| Tier | Reward |\r\n|------|----------------------------------|\r\n| 1 | Sprouts Record |\r\n| 2 | 120 Core Charges |\r\n| 3 | Burn Precon |\r\n| 4 | 120 Core Charges, 25 Flux |\r\n| 5 | Profile Pictures |\r\n| 6 | Emotes |\r\n| 7 | 120 Core Charges |\r\n| 8 | Profile Pictures |\r\n| 9 | 60 Core Charges, 50 Flux |\r\n| 10 | 240 Core Charges, 1 Draft Ticket |\r\n\r\n| Core Charges | Flux |\r\n|--------------|------|\r\n| 660 | 75 |",
|
||||
Frontmatter = new()
|
||||
{
|
||||
{ "expPer", "30" },
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<NavigationTracker/>
|
||||
<article class="content px-4">
|
||||
<TelerikRootComponent>
|
||||
@Body
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<PageTitle>Chrono CCG - Agents</PageTitle>
|
||||
|
||||
<HeadContent>
|
||||
<meta name="description" content="View and compare all Chrono CCG agents. Analyze stats, factions, and efficiency to build the perfect deck." />
|
||||
<meta name="description"
|
||||
content="View and compare all Chrono CCG agents. Analyze stats, factions, and efficiency to build the perfect deck."/>
|
||||
</HeadContent>
|
||||
|
||||
<div class="agents-page">
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<PageTitle>Chrono CCG - @(deck?.Name ?? "Deck Details")</PageTitle>
|
||||
|
||||
<HeadContent>
|
||||
<meta name="description" content="@(deck != null ? $"View details for the {deck.Name} deck. {deck.Cards.Count} cards, {deck.Factions.Count} factions." : "Deck details and card list.")" />
|
||||
<meta name="description"
|
||||
content="@(deck != null ? $"View details for the {deck.Name} deck. {deck.Cards.Count} cards, {deck.Factions.Count} factions." : "Deck details and card list.")"/>
|
||||
</HeadContent>
|
||||
|
||||
<div class="deck-detail-page">
|
||||
@@ -23,7 +24,8 @@
|
||||
<article class="deck-article">
|
||||
<header class="deck-header">
|
||||
<h1>@deck.Name</h1>
|
||||
<b>Deck Code:</b> <pre>@deck.DeckCode</pre>
|
||||
<b>Deck Code:</b>
|
||||
<pre>@deck.DeckCode</pre>
|
||||
@if (deck.Factions.Count > 0)
|
||||
{
|
||||
<div class="deck-factions">
|
||||
@@ -124,7 +126,7 @@
|
||||
{
|
||||
if (deck == null) return [];
|
||||
var counts = new Dictionary<int, int>();
|
||||
for (int i = 0; i <= 10; i++) counts[i] = 0;
|
||||
for (var i = 0; i <= 10; i++) counts[i] = 0;
|
||||
|
||||
foreach (var cardName in deck.Cards)
|
||||
{
|
||||
@@ -139,4 +141,5 @@
|
||||
|
||||
return counts.OrderBy(x => x.Key).Select(x => (x.Key, x.Value)).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<PageTitle>Chrono CCG - Decks</PageTitle>
|
||||
|
||||
<HeadContent>
|
||||
<meta name="description" content="Explore community and official Chrono CCG decks. Find the best strategies and card combinations." />
|
||||
<meta name="description"
|
||||
content="Explore community and official Chrono CCG decks. Find the best strategies and card combinations."/>
|
||||
</HeadContent>
|
||||
|
||||
<div class="decks-page">
|
||||
@@ -13,7 +14,8 @@
|
||||
<div class="decks-header-row">
|
||||
<p class="text-secondary">@decks.Count deck@(decks.Count != 1 ? "s" : "")</p>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="showPrecons" @bind="showPrecons" @bind:after="RefreshDecks">
|
||||
<input class="form-check-input" type="checkbox" id="showPrecons" @bind="showPrecons"
|
||||
@bind:after="RefreshDecks">
|
||||
<label class="form-check-label" for="showPrecons">Show Precons</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +72,7 @@
|
||||
|
||||
@code {
|
||||
private List<DeckData> decks = [];
|
||||
private bool showPrecons = false;
|
||||
private bool showPrecons;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -83,8 +85,8 @@
|
||||
.Where(d => d.IsVisible)
|
||||
.Where(d =>
|
||||
showPrecons
|
||||
? d.DeckType is "precon" or "store" :
|
||||
d.DeckType is "custom")
|
||||
? d.DeckType is "precon" or "store"
|
||||
: d.DeckType is "custom")
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
</div>
|
||||
|
||||
<div class="category-list">
|
||||
<div class="category-item @(selectedCategory == null ? "active" : "")" @onclick="() => SelectCategory(null)">
|
||||
<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)">
|
||||
<div class="category-item @(selectedCategory == cat ? "active" : "")"
|
||||
@onclick="() => SelectCategory(cat)">
|
||||
@cat
|
||||
</div>
|
||||
}
|
||||
@@ -134,7 +136,9 @@
|
||||
return doc.Frontmatter.TryGetValue(key, out var val) && !string.IsNullOrWhiteSpace(val) ? val : null;
|
||||
}
|
||||
|
||||
private static string GetLabel(string key) => key switch
|
||||
private static string GetLabel(string key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
"category" => "Category",
|
||||
"description" => "Description",
|
||||
@@ -148,6 +152,8 @@
|
||||
"faction" => "Faction",
|
||||
"exp" => "EXP Reward",
|
||||
"see" => "See Also",
|
||||
_ => string.Concat(key.Select((c, i) => i > 0 && char.IsUpper(c) ? " " + c : c.ToString())),
|
||||
_ => string.Concat(key.Select((c, i) => i > 0 && char.IsUpper(c) ? " " + c : c.ToString()))
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Shared.Services;
|
||||
|
||||
public class AnalyticsService
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
|
||||
public AnalyticsService(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async ValueTask TrackPageView(string path)
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("trackPageView", path);
|
||||
}
|
||||
|
||||
public async ValueTask TrackEvent(string eventName, object? eventData = null)
|
||||
{
|
||||
if (eventData != null)
|
||||
await _jsRuntime.InvokeVoidAsync("gtag", "event", eventName, eventData);
|
||||
else
|
||||
await _jsRuntime.InvokeVoidAsync("gtag", "event", eventName);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Text.RegularExpressions;
|
||||
using Chrono.Model;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Shared.Services;
|
||||
|
||||
public class CardRenderingService
|
||||
{
|
||||
private readonly List<string> _cardNames;
|
||||
private readonly Dictionary<string, CardData> _cardLookup;
|
||||
private readonly List<string> _cardNames;
|
||||
private readonly Regex _nameRegex;
|
||||
|
||||
public CardRenderingService()
|
||||
@@ -33,7 +33,7 @@ public class CardRenderingService
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
int sequence = 0;
|
||||
var sequence = 0;
|
||||
var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var paragraph in paragraphs)
|
||||
@@ -41,31 +41,27 @@ public class CardRenderingService
|
||||
builder.OpenElement(sequence++, "p");
|
||||
|
||||
var matches = _nameRegex.Matches(paragraph);
|
||||
int lastIndex = 0;
|
||||
var lastIndex = 0;
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
if (match.Index > lastIndex)
|
||||
{
|
||||
builder.AddContent(sequence++, paragraph.Substring(lastIndex, match.Index - lastIndex));
|
||||
}
|
||||
|
||||
var cardName = match.Value;
|
||||
var card = LookupCard(cardName);
|
||||
|
||||
builder.OpenElement(sequence++, "button");
|
||||
builder.AddAttribute(sequence++, "class", "inline-card-btn");
|
||||
builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => onCardClick(card)));
|
||||
builder.AddAttribute(sequence++, "onclick",
|
||||
EventCallback.Factory.Create(this, () => onCardClick(card)));
|
||||
builder.AddContent(sequence++, cardName);
|
||||
builder.CloseElement();
|
||||
|
||||
lastIndex = match.Index + match.Length;
|
||||
}
|
||||
|
||||
if (lastIndex < paragraph.Length)
|
||||
{
|
||||
builder.AddContent(sequence++, paragraph.Substring(lastIndex));
|
||||
}
|
||||
if (lastIndex < paragraph.Length) builder.AddContent(sequence++, paragraph.Substring(lastIndex));
|
||||
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
using Tests.PageObjects;
|
||||
|
||||
namespace Tests;
|
||||
@@ -8,10 +7,6 @@ namespace Tests;
|
||||
[TestFixture]
|
||||
public class FeatureTests : PageTest
|
||||
{
|
||||
private DecksPage _decksPage;
|
||||
private DeckDetailPage _deckDetailPage;
|
||||
private AgentsPage _agentsPage;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
@@ -20,6 +15,10 @@ public class FeatureTests : PageTest
|
||||
_agentsPage = new AgentsPage(Page);
|
||||
}
|
||||
|
||||
private DecksPage _decksPage;
|
||||
private DeckDetailPage _deckDetailPage;
|
||||
private AgentsPage _agentsPage;
|
||||
|
||||
[Test]
|
||||
public async Task DeckManaCurve_ShouldBeVisibleOnDetail()
|
||||
{
|
||||
|
||||
@@ -4,12 +4,17 @@ namespace Tests.PageObjects;
|
||||
|
||||
public class AgentsPage : BasePage
|
||||
{
|
||||
public AgentsPage(IPage page) : base(page) { }
|
||||
|
||||
public async Task GotoAsync() => await NavigateToAsync("/agents");
|
||||
public AgentsPage(IPage page) : base(page)
|
||||
{
|
||||
}
|
||||
|
||||
public ILocator Grid => Page.Locator(".agents-grid");
|
||||
|
||||
public async Task GotoAsync()
|
||||
{
|
||||
await NavigateToAsync("/agents");
|
||||
}
|
||||
|
||||
public async Task WaitForGridAsync()
|
||||
{
|
||||
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace Tests.PageObjects;
|
||||
|
||||
public abstract class BasePage
|
||||
{
|
||||
protected readonly IPage Page;
|
||||
protected readonly string BaseUrl = "http://localhost:5256";
|
||||
protected readonly IPage Page;
|
||||
|
||||
protected BasePage(IPage page)
|
||||
{
|
||||
@@ -17,5 +17,8 @@ public abstract class BasePage
|
||||
await Page.GotoAsync($"{BaseUrl}{path}");
|
||||
}
|
||||
|
||||
public ILocator GetHeading() => Page.Locator("h1");
|
||||
public ILocator GetHeading()
|
||||
{
|
||||
return Page.Locator("h1");
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,17 @@ namespace Tests.PageObjects;
|
||||
|
||||
public class DeckDetailPage : BasePage
|
||||
{
|
||||
public DeckDetailPage(IPage page) : base(page) { }
|
||||
public DeckDetailPage(IPage page) : base(page)
|
||||
{
|
||||
}
|
||||
|
||||
public ILocator ManaCurve => Page.Locator(".mana-curve");
|
||||
public ILocator ManaBars => Page.Locator(".mana-bar");
|
||||
public ILocator ManaBarLabels => Page.Locator(".mana-bar-label");
|
||||
public ILocator ManaCosts => Page.Locator(".mana-cost");
|
||||
|
||||
public async Task<int> GetManaBarCountAsync() => await ManaBars.CountAsync();
|
||||
public async Task<int> GetManaBarCountAsync()
|
||||
{
|
||||
return await ManaBars.CountAsync();
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,23 @@ namespace Tests.PageObjects;
|
||||
|
||||
public class DecksPage : BasePage
|
||||
{
|
||||
public DecksPage(IPage page) : base(page) { }
|
||||
public DecksPage(IPage page) : base(page)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task GotoAsync() => await NavigateToAsync("/decks");
|
||||
public async Task GotoAsync()
|
||||
{
|
||||
await NavigateToAsync("/decks");
|
||||
}
|
||||
|
||||
public ILocator GetDeckCards() => Page.Locator(".deck-card");
|
||||
public ILocator GetDeckCards()
|
||||
{
|
||||
return Page.Locator(".deck-card");
|
||||
}
|
||||
|
||||
public async Task ClickDeckByNameAsync(string name)
|
||||
{
|
||||
await Page.Locator(".deck-card-title").GetByText(name, new() { Exact = true }).ClickAsync();
|
||||
await Page.Locator(".deck-card-title").GetByText(name, new LocatorGetByTextOptions { Exact = true })
|
||||
.ClickAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.NUnit;
|
||||
|
||||
@@ -13,7 +14,7 @@ public class PlaywrightTests : PageTest
|
||||
public async Task HomePage_ShouldLoad()
|
||||
{
|
||||
await Page.GotoAsync(BaseUrl);
|
||||
await Expect(Page).ToHaveTitleAsync(new System.Text.RegularExpressions.Regex("Chrono CCG"));
|
||||
await Expect(Page).ToHaveTitleAsync(new Regex("Chrono CCG"));
|
||||
|
||||
// Check for some content that should be on the home page
|
||||
var heading = Page.Locator("h1");
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Tests;
|
||||
[TestFixture]
|
||||
public class TelerikLicenseTests : PageTest
|
||||
{
|
||||
|
||||
private const string BaseUrl = "http://localhost:5256";
|
||||
|
||||
[Test]
|
||||
|
||||
+12
-17
@@ -1,13 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Tests;
|
||||
|
||||
[SetUpFixture]
|
||||
public class TestSetup
|
||||
{
|
||||
private Process? _serverProcess;
|
||||
private const string ServerUrl = "http://localhost:5256";
|
||||
private Process? _serverProcess;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task BeforeAllTests()
|
||||
@@ -17,14 +16,9 @@ public class TestSetup
|
||||
// Find solution root
|
||||
var currentDir = AppContext.BaseDirectory;
|
||||
while (currentDir != null && !File.Exists(Path.Combine(currentDir, "Chrono.sln")))
|
||||
{
|
||||
currentDir = Path.GetDirectoryName(currentDir);
|
||||
}
|
||||
|
||||
if (currentDir == null)
|
||||
{
|
||||
throw new Exception("Could not find Chrono.sln to determine project path.");
|
||||
}
|
||||
if (currentDir == null) throw new Exception("Could not find Chrono.sln to determine project path.");
|
||||
|
||||
var projectPath = Path.Combine(currentDir, "Server", "Server.csproj");
|
||||
|
||||
@@ -41,24 +35,26 @@ public class TestSetup
|
||||
|
||||
_serverProcess = Process.Start(startInfo);
|
||||
|
||||
_serverProcess.OutputDataReceived += (s, e) => { if (e.Data != null) Console.WriteLine($"[SERVER OUT] {e.Data}"); };
|
||||
_serverProcess.ErrorDataReceived += (s, e) => { if (e.Data != null) Console.WriteLine($"[SERVER ERR] {e.Data}"); };
|
||||
_serverProcess.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
if (e.Data != null) Console.WriteLine($"[SERVER OUT] {e.Data}");
|
||||
};
|
||||
_serverProcess.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
if (e.Data != null) Console.WriteLine($"[SERVER ERR] {e.Data}");
|
||||
};
|
||||
_serverProcess.BeginOutputReadLine();
|
||||
_serverProcess.BeginErrorReadLine();
|
||||
|
||||
if (_serverProcess == null)
|
||||
{
|
||||
throw new Exception("Failed to start server process.");
|
||||
}
|
||||
if (_serverProcess == null) throw new Exception("Failed to start server process.");
|
||||
|
||||
// Wait for the server to be ready
|
||||
using var client = new HttpClient();
|
||||
var sw = Stopwatch.StartNew();
|
||||
var timeout = TimeSpan.FromSeconds(30);
|
||||
bool isReady = false;
|
||||
var isReady = false;
|
||||
|
||||
while (sw.Elapsed < timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await client.GetAsync(ServerUrl);
|
||||
@@ -73,7 +69,6 @@ public class TestSetup
|
||||
// Wait and retry
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReady)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }" NotFoundPage="typeof(NotFound)">
|
||||
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }"
|
||||
NotFoundPage="typeof(NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@namespace Chrono.Components
|
||||
|
||||
@if (Card != null)
|
||||
@@ -88,7 +89,8 @@
|
||||
var target = LookupCard(targetName);
|
||||
if (target != null)
|
||||
{
|
||||
<button class="inline-card-btn" @onclick="() => Navigate(target)">@targetName</button>
|
||||
<button class="inline-card-btn"
|
||||
@onclick="() => Navigate(target)">@targetName</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,7 +110,8 @@
|
||||
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
||||
if (fromCard != null)
|
||||
{
|
||||
<button class="inline-card-btn" @onclick="() => Navigate(fromCard)">@Card.ImmortalizeFrom</button>
|
||||
<button class="inline-card-btn"
|
||||
@onclick="() => Navigate(fromCard)">@Card.ImmortalizeFrom</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -181,7 +184,9 @@
|
||||
{
|
||||
await Http.PostAsJsonAsync("api/notes", new CardNote { CardName = Card.Name, Note = currentNote });
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSaving = false;
|
||||
@@ -209,7 +214,9 @@
|
||||
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private RenderFragment RenderDescription(string description) => builder =>
|
||||
private RenderFragment RenderDescription(string description)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
var cardNames = CardDatabase.Cards
|
||||
.Select(c => c.Name)
|
||||
@@ -217,18 +224,18 @@
|
||||
.OrderByDescending(n => n.Length)
|
||||
.ToList();
|
||||
|
||||
int seq = 0;
|
||||
var seq = 0;
|
||||
var remaining = description;
|
||||
|
||||
while (!string.IsNullOrEmpty(remaining))
|
||||
{
|
||||
int bestIdx = -1;
|
||||
var bestIdx = -1;
|
||||
string? bestName = null;
|
||||
|
||||
foreach (var name in cardNames)
|
||||
{
|
||||
var pattern = $@"\b{System.Text.RegularExpressions.Regex.Escape(name)}\b";
|
||||
var match = System.Text.RegularExpressions.Regex.Match(remaining, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
var pattern = $@"\b{Regex.Escape(name)}\b";
|
||||
var match = Regex.Match(remaining, pattern, RegexOptions.IgnoreCase);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
@@ -270,3 +277,5 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card-detail {
|
||||
@@ -231,7 +235,8 @@
|
||||
text-decoration-color: #7b73ff;
|
||||
}
|
||||
|
||||
@@media (max-width: 768px) {
|
||||
@
|
||||
@media (max-width: 768px) {
|
||||
.detail-layout {
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/cards"
|
||||
@inject HttpClient Http
|
||||
@inject AnalyticsService AnalyticsService
|
||||
|
||||
<PageTitle>Chrono CCG - Card Gallery</PageTitle>
|
||||
|
||||
@@ -21,7 +22,8 @@
|
||||
<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"))'>
|
||||
<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
|
||||
@@ -234,11 +236,13 @@
|
||||
private void SetCategory(string cat)
|
||||
{
|
||||
categoryFilter = categoryFilter == cat ? "" : cat;
|
||||
_ = AnalyticsService.TrackEvent("filter_category", new { category = categoryFilter });
|
||||
}
|
||||
|
||||
private void ClearCategoryFilter()
|
||||
{
|
||||
categoryFilter = "";
|
||||
_ = AnalyticsService.TrackEvent("filter_category", new { category = "All" });
|
||||
}
|
||||
|
||||
private void ClearFactionFilter()
|
||||
@@ -264,6 +268,7 @@
|
||||
private void SelectCard(CardData card)
|
||||
{
|
||||
selectedCard = card;
|
||||
_ = AnalyticsService.TrackEvent("select_item", new { item_name = card.Name, item_category = card.Category });
|
||||
}
|
||||
|
||||
private void CloseDetail()
|
||||
@@ -278,4 +283,5 @@
|
||||
factionFilter = "";
|
||||
costFilter = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Shared.Services;
|
||||
using Web;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
@@ -8,6 +9,7 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
builder.Services.AddTelerikBlazor();
|
||||
builder.Services.AddSingleton<Shared.Services.CardRenderingService>();
|
||||
builder.Services.AddSingleton<CardRenderingService>();
|
||||
builder.Services.AddScoped<AnalyticsService>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
@@ -9,6 +9,8 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using Shared.Layout
|
||||
@using Shared.Pages
|
||||
@using Shared.Services
|
||||
@using Shared.Components
|
||||
@using Chrono.Components
|
||||
@using Telerik.Blazor
|
||||
@using Telerik.Blazor.Components
|
||||
|
||||
@@ -257,9 +257,6 @@ a, .btn-link {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.inline-card-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@@ -6,26 +6,29 @@
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Chrono CCG - Card Gallery</title>
|
||||
<base href="/"/>
|
||||
<meta name="description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
||||
<meta name="keywords" content="Chrono CCG, Reference, Card Gallery, DB"/>
|
||||
<meta content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."
|
||||
name="description"/>
|
||||
<meta content="Chrono CCG, Reference, Card Gallery, DB" name="keywords"/>
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:url" content="https://chronoccg.com/"/>
|
||||
<meta property="og:title" content="Chrono CCG - Card Gallery"/>
|
||||
<meta property="og:description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
||||
<meta property="og:image" content="https://chronoccg.com/favicon.png"/>
|
||||
<meta content="website" property="og:type"/>
|
||||
<meta content="https://chronoccg.com/" property="og:url"/>
|
||||
<meta content="Chrono CCG - Card Gallery" property="og:title"/>
|
||||
<meta content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."
|
||||
property="og:description"/>
|
||||
<meta content="https://chronoccg.com/favicon.png" property="og:image"/>
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image"/>
|
||||
<meta property="twitter:url" content="https://chronoccg.com/"/>
|
||||
<meta property="twitter:title" content="Chrono CCG - Card Gallery"/>
|
||||
<meta property="twitter:description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
||||
<meta property="twitter:image" content="https://chronoccg.com/favicon.png"/>
|
||||
<meta content="summary_large_image" property="twitter:card"/>
|
||||
<meta content="https://chronoccg.com/" property="twitter:url"/>
|
||||
<meta content="Chrono CCG - Card Gallery" property="twitter:title"/>
|
||||
<meta content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."
|
||||
property="twitter:description"/>
|
||||
<meta content="https://chronoccg.com/favicon.png" property="twitter:image"/>
|
||||
|
||||
<link href="manifest.json" rel="manifest"/>
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
<link href="icon-512.png" rel="apple-touch-icon" sizes="512x512"/>
|
||||
<link href="icon-192.png" rel="apple-touch-icon" sizes="192x192"/>
|
||||
<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"/>
|
||||
@@ -37,6 +40,26 @@
|
||||
<script defer src="/_content/Telerik.UI.for.Blazor/js/telerik-blazor.js"></script>
|
||||
<link href="/favicon.png" rel="icon" type="image/png"/>
|
||||
<script type="importmap"></script>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-HVMP2QEJLR"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-HVMP2QEJLR');
|
||||
|
||||
window.trackPageView = (path) => {
|
||||
gtag('event', 'page_view', {
|
||||
page_path: path,
|
||||
page_location: window.location.origin + path
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// In development, always fetch from the network and do not cache.
|
||||
// This is appropriate during development as the service worker will wake up and fetch resources from the network.
|
||||
self.addEventListener('fetch', () => { });
|
||||
self.addEventListener('fetch', () => {
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
{
|
||||
"navigationFallback": {
|
||||
"rewrite": "/index.html",
|
||||
"exclude": ["/css/*", "/lib/*", "/_framework/*", "/_content/*", "/sample-data/*"]
|
||||
"exclude": [
|
||||
"/css/*",
|
||||
"/lib/*",
|
||||
"/_framework/*",
|
||||
"/_content/*",
|
||||
"/sample-data/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ archetypes:
|
||||
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]].
|
||||
|
||||
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]]
|
||||
|
||||
@@ -17,6 +17,7 @@ archetypes:
|
||||
- "[[Rewound]]"
|
||||
imageLink: "[[Divergence Assassin.png]]"
|
||||
---
|
||||
|
||||
![[Divergence Assassin.png]]
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ archetypes:
|
||||
imageLink: "[[Scuttling Spares.png]]"
|
||||
rank: S
|
||||
---
|
||||
|
||||
I haven't played this card, but it sounds like it obviously enables discard.
|
||||
|
||||
![[Scuttling Spares.png]]
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
category: Debuff
|
||||
description: Set this Agent's strength to 0 until Round End.
|
||||
---
|
||||
|
||||
Reduce attack to zero.
|
||||
@@ -74,20 +74,30 @@ factions:
|
||||
deckCode: AUHUE2LHEBCW4ZLSM54SAQ3POB4QWY3PNZZXI4TVMN2GKZBTAADACASPCICESCQCAUFDCAYGAMCAGAQDDYBQCAYBAOTQCAYQAMDQG
|
||||
deckType: custom
|
||||
---
|
||||
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]].
|
||||
|
||||
You 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.
|
||||
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]].
|
||||
|
||||
You need to Overflow your mana once in a while, or some of your [[Supernova]] removal will be useless. Be pass heavy. And if you do end up overflowing many times [[Supernova]] can refill for mana bar, and it's worth adding some [[Snap Back]]s to the deck to use that spell multiple times.
|
||||
You 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.
|
||||
|
||||
You need to Overflow your mana once in a while, or some of your [[Supernova]] removal will be useless. Be pass heavy.
|
||||
And if you do end up overflowing many times [[Supernova]] can refill for mana bar, and it's worth adding
|
||||
some [[Snap Back]]s to the deck to use that spell multiple times.
|
||||
|
||||
Attack aggressively with [[Brilliant Martyr]]. You really want [[Star Siphon]] to ramp.
|
||||
|
||||
As divers, [[Peaceful Synthesizer]] is a 1 drop that will quickly turn into a 3/3. Can't get more stat efficient than that. [[Nonlethal Special Forces]] is to enable [[Phasetide]] for AOE [[Mute]] cards given we don't care about stat buffs. Also as it stands, it always seems to punch a above it's weight on the board given the [[Disarm]] effect.
|
||||
As divers, [[Peaceful Synthesizer]] is a 1 drop that will quickly turn into a 3/3. Can't get more stat efficient than
|
||||
that. [[Nonlethal Special Forces]] is to enable [[Phasetide]] for AOE [[Mute]] cards given we don't care about stat
|
||||
buffs. Also as it stands, it always seems to punch a above it's weight on the board given the [[Disarm]] effect.
|
||||
|
||||
So ya, [[Curb the Anomalies]] can weaken the enemy board, by shutting off all there better stats, activates, and synergies. Hopefully destroy the entire enemy board in combat.
|
||||
So ya, [[Curb the Anomalies]] can weaken the enemy board, by shutting off all there better stats, activates, and
|
||||
synergies. Hopefully destroy the entire enemy board in combat.
|
||||
|
||||
And I suppose [[Bearer of the Broth]]. Since the deck has so much raw stats, you will probably find somewhere to heal. Like healing [[Kinetic Absorber]] so it can keep killing after it Immortalizes.
|
||||
And I suppose [[Bearer of the Broth]]. Since the deck has so much raw stats, you will probably find somewhere to heal.
|
||||
Like healing [[Kinetic Absorber]] so it can keep killing after it Immortalizes.
|
||||
|
||||
Also in practice [[Bearer of the Broth]] has proven to be an insane and reliable draw engine for this deck.
|
||||
|
||||
[[Gunnery Captain]] helps clear up the board. Tokens are just 1 to 2 cost units that grew to absurd sizes. And [[Army of the Sun]] helps in the mirror match, given how common this deck is due to the precon.
|
||||
[[Gunnery Captain]] helps clear up the board. Tokens are just 1 to 2 cost units that grew to absurd sizes.
|
||||
And [[Army of the Sun]] helps in the mirror match, given how common this deck is due to the precon.
|
||||
@@ -62,11 +62,13 @@ factions:
|
||||
deckCode: AUMU65TFOJTGY33XNFXGOICTMVRXK4TJOR4SAQ3POB4QWY3PNZZXI4TVMN2GKZBRACQAAANZAEBZEAI6AQFTSAYCAMOQGBADAEBQCAZ2AMCAGGQDAQBQCAY
|
||||
deckType: custom
|
||||
---
|
||||
|
||||
Built deck as a joke, but it kind of feels nuts in limited testing.
|
||||
|
||||
Mulligan everything for [[Radiant Channeling]] and [[Supernova]]. You are hoping that limited removal keeps you alive.
|
||||
|
||||
If they heavy buff something out from your removal range, you might still have enough reserve to [[Throw into the Sun]] that big target.
|
||||
If they heavy buff something out from your removal range, you might still have enough reserve to [[Throw into the Sun]]
|
||||
that big target.
|
||||
|
||||
If you survive, start scaling your board with [[Awakened Security System]] or [[Limit Breaker]].
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
category: Faction
|
||||
---
|
||||
|
||||
Concepts:
|
||||
|
||||
- Wide Boards
|
||||
- Overpower
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
category: Faction
|
||||
---
|
||||
|
||||
Concepts:
|
||||
|
||||
- Healing
|
||||
-
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
category: Faction
|
||||
---
|
||||
|
||||
Concepts:
|
||||
|
||||
- Activatable Agents
|
||||
-
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
category: Faction
|
||||
---
|
||||
|
||||
Concepts:
|
||||
|
||||
- Discard
|
||||
@@ -1,11 +1,14 @@
|
||||
---
|
||||
category: Faction
|
||||
---
|
||||
|
||||
# Energy Reserves
|
||||
|
||||
Care about always having banked energy. Spells can refund themselves when using [[Energy Reserves]], or get stronger is you [[Overflow]] your energy reserve.
|
||||
Care about always having banked energy. Spells can refund themselves when using [[Energy Reserves]], or get stronger is
|
||||
you [[Overflow]] your energy reserve.
|
||||
|
||||
Concepts:
|
||||
|
||||
- Ramp
|
||||
- Using and Replenishing Reserved Energy
|
||||
-
|
||||
@@ -17,4 +17,5 @@ archetypes:
|
||||
- "[[Keyword/Sprout]]"
|
||||
imageLink: "[[Horticulturist Oswald.png]]"
|
||||
---
|
||||
|
||||
![[Horticulturist Oswald.png]]
|
||||
@@ -17,4 +17,5 @@ archetypes:
|
||||
- "[[Rewound]]"
|
||||
imageLink: "[[Violet Inquisitioner]]"
|
||||
---
|
||||
|
||||
![[Violent Inquisitioner.png]]
|
||||
@@ -17,4 +17,5 @@ archetypes:
|
||||
- N/A
|
||||
imageLink: "[[Scoot Sparkles.png]]"
|
||||
---
|
||||
|
||||
![[Scoot Sparkles.png]]
|
||||
@@ -17,4 +17,5 @@ archetypes:
|
||||
- N/A
|
||||
imageLink: "[[The Ripper.png]]"
|
||||
---
|
||||
|
||||
![[The Ripper.png]]
|
||||
|
||||
@@ -5,7 +5,7 @@ category: Pass
|
||||
---
|
||||
|
||||
| Tier | Reward |
|
||||
| ---- | -------------------------------- |
|
||||
|------|----------------------------------|
|
||||
| 1 | Sprouts Record |
|
||||
| 2 | 120 Core Charges |
|
||||
| 3 | Burn Precon |
|
||||
@@ -18,5 +18,5 @@ category: Pass
|
||||
| 10 | 240 Core Charges, 1 Draft Ticket |
|
||||
|
||||
| Core Charges | Flux |
|
||||
| ------------ | ---- |
|
||||
|--------------|------|
|
||||
| 660 | 75 |
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
category: Quest
|
||||
---
|
||||
|
||||
1: 30cc 30xp
|
||||
2: 20cc 25xp
|
||||
3: 20cc 20xp
|
||||
|
||||
Reference in New Issue
Block a user