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"))),
|
ImmortalizeWhen = NullIfNa(StripWikiLinks(yaml.GetValueOrDefault("immortalizeWhen"))),
|
||||||
ImageFile = imageFile,
|
ImageFile = imageFile,
|
||||||
Artist = yaml.GetValueOrDefault("artist"),
|
Artist = yaml.GetValueOrDefault("artist"),
|
||||||
Rarity = yaml.GetValueOrDefault("rarity"),
|
Rarity = yaml.GetValueOrDefault("rarity")
|
||||||
};
|
};
|
||||||
|
|
||||||
cards.Add(card);
|
cards.Add(card);
|
||||||
|
|||||||
@@ -31,9 +31,8 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
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: 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;
|
animation-fill-mode: both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private int currentCount = 0;
|
private int currentCount;
|
||||||
|
|
||||||
private void IncrementCount()
|
private void IncrementCount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
|
|
||||||
<h3>Development Mode</h3>
|
<h3>Development Mode</h3>
|
||||||
<p>
|
<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>
|
||||||
<p>
|
<p>
|
||||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
It can result in displaying sensitive information from exceptions to end users.
|
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.
|
and restarting the app.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -30,7 +32,9 @@
|
|||||||
private string? RequestId { get; set; }
|
private string? RequestId { get; set; }
|
||||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
protected override void OnInitialized() =>
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
@using Shared.Pages
|
@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">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"/>
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||||
</Found>
|
</Found>
|
||||||
</Router>
|
</Router>
|
||||||
@@ -11,7 +11,7 @@ var app = builder.Build();
|
|||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
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.
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
app.UseHsts();
|
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 {
|
h1 {
|
||||||
font-size: calc(1.375rem + 1.5vw);
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
@@ -233,6 +234,7 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: calc(1.325rem + 0.9vw);
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@@ -242,6 +244,7 @@ h2 {
|
|||||||
h3 {
|
h3 {
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
@@ -251,6 +254,7 @@ h3 {
|
|||||||
h4 {
|
h4 {
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@@ -351,6 +355,7 @@ a {
|
|||||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||||
}
|
}
|
||||||
@@ -375,6 +380,7 @@ pre {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -386,6 +392,7 @@ code {
|
|||||||
color: var(--bs-code-color);
|
color: var(--bs-code-color);
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
a > code {
|
a > code {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
@@ -397,6 +404,7 @@ kbd {
|
|||||||
background-color: var(--bs-body-color);
|
background-color: var(--bs-body-color);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd kbd {
|
kbd kbd {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -474,6 +482,7 @@ select {
|
|||||||
select {
|
select {
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:disabled {
|
select:disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -488,6 +497,7 @@ button,
|
|||||||
[type=submit] {
|
[type=submit] {
|
||||||
-webkit-appearance: button;
|
-webkit-appearance: button;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:not(:disabled),
|
button:not(:disabled),
|
||||||
[type=button]:not(:disabled),
|
[type=button]:not(:disabled),
|
||||||
[type=reset]:not(:disabled),
|
[type=reset]:not(:disabled),
|
||||||
@@ -519,11 +529,13 @@ legend {
|
|||||||
font-size: calc(1.275rem + 0.3vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
legend {
|
legend {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
legend + * {
|
legend + * {
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ h6, h5, h4, h3, h2, h1 {
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: calc(1.375rem + 1.5vw);
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
@@ -233,6 +234,7 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: calc(1.325rem + 0.9vw);
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@@ -242,6 +244,7 @@ h2 {
|
|||||||
h3 {
|
h3 {
|
||||||
font-size: calc(1.3rem + 0.6vw);
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
@@ -251,6 +254,7 @@ h3 {
|
|||||||
h4 {
|
h4 {
|
||||||
font-size: calc(1.275rem + 0.3vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@@ -351,6 +355,7 @@ a {
|
|||||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||||
}
|
}
|
||||||
@@ -375,6 +380,7 @@ pre {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -386,6 +392,7 @@ code {
|
|||||||
color: var(--bs-code-color);
|
color: var(--bs-code-color);
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
a > code {
|
a > code {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
@@ -397,6 +404,7 @@ kbd {
|
|||||||
background-color: var(--bs-body-color);
|
background-color: var(--bs-body-color);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
kbd kbd {
|
kbd kbd {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -474,6 +482,7 @@ select {
|
|||||||
select {
|
select {
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
select:disabled {
|
select:disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -488,6 +497,7 @@ button,
|
|||||||
[type=submit] {
|
[type=submit] {
|
||||||
-webkit-appearance: button;
|
-webkit-appearance: button;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:not(:disabled),
|
button:not(:disabled),
|
||||||
[type=button]:not(:disabled),
|
[type=button]:not(:disabled),
|
||||||
[type=reset]:not(:disabled),
|
[type=reset]:not(:disabled),
|
||||||
@@ -519,11 +529,13 @@ legend {
|
|||||||
font-size: calc(1.275rem + 0.3vw);
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
legend {
|
legend {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
legend + * {
|
legend + * {
|
||||||
clear: right;
|
clear: right;
|
||||||
}
|
}
|
||||||
@@ -553,6 +565,7 @@ legend + * {
|
|||||||
[type="number"] {
|
[type="number"] {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
::-webkit-search-decoration {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
@@ -591,4 +604,5 @@ progress {
|
|||||||
[hidden] {
|
[hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
/*# 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 Title { get; init; } = "";
|
||||||
public string Category { get; init; } = "";
|
public string Category { get; init; } = "";
|
||||||
public string Content { 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">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Server;
|
using Server;
|
||||||
|
using Server.Components;
|
||||||
|
using Shared.Pages;
|
||||||
|
using Shared.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -7,7 +10,7 @@ builder.Services.AddControllers();
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||||
builder.Services.AddTelerikBlazor();
|
builder.Services.AddTelerikBlazor();
|
||||||
builder.Services.AddSingleton<Shared.Services.CardRenderingService>();
|
builder.Services.AddSingleton<CardRenderingService>();
|
||||||
|
|
||||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
@@ -32,17 +35,14 @@ using (var scope = app.Services.CreateScope())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/Error");
|
||||||
{
|
|
||||||
app.UseExceptionHandler("/Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.MapRazorComponents<Server.Components.App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode()
|
.AddInteractiveServerRenderMode()
|
||||||
.AddAdditionalAssemblies(typeof(Shared.Pages.Home).Assembly);
|
.AddAdditionalAssemblies(typeof(Home).Assembly);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -257,9 +257,6 @@ a, .btn-link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.inline-card-btn {
|
.inline-card-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@namespace Shared.Components
|
@namespace Shared.Components
|
||||||
@using Chrono.Model
|
|
||||||
|
|
||||||
@if (Card != null)
|
@if (Card != null)
|
||||||
{
|
{
|
||||||
@@ -105,7 +104,8 @@
|
|||||||
var target = LookupCard(targetName);
|
var target = LookupCard(targetName);
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
<button class="inline-card-btn" @onclick="() => SelectCard(target)">@targetName</button>
|
<button class="inline-card-btn"
|
||||||
|
@onclick="() => SelectCard(target)">@targetName</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -125,7 +125,8 @@
|
|||||||
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
||||||
if (fromCard != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -148,9 +149,15 @@
|
|||||||
|
|
||||||
private bool HasImage => Card?.ImageFile is { Length: > 0 } && Card.ImageFile != "placeholder.png";
|
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)
|
private RenderFragment RenderCardDescription(string description)
|
||||||
{
|
{
|
||||||
@@ -163,4 +170,5 @@
|
|||||||
return CardDatabase.Cards.FirstOrDefault(c =>
|
return CardDatabase.Cards.FirstOrDefault(c =>
|
||||||
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-detail {
|
.card-detail {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@namespace Shared.Components
|
@namespace Shared.Components
|
||||||
@using Chrono.Model
|
|
||||||
|
|
||||||
<div class="deck-card-row">
|
<div class="deck-card-row">
|
||||||
@foreach (var group in Cards.GroupBy(x => x))
|
@foreach (var group in Cards.GroupBy(x => x))
|
||||||
@@ -29,10 +28,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter, EditorRequired] public List<string> Cards { get; set; } = [];
|
[Parameter][EditorRequired] public List<string> Cards { get; set; } = [];
|
||||||
[Parameter] public EventCallback<CardData?> OnSelectCard { 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
|
private static readonly Dictionary<string, CardData> CardLookup = CardDatabase.Cards
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c.Name))
|
.Where(c => !string.IsNullOrWhiteSpace(c.Name))
|
||||||
@@ -43,4 +45,5 @@
|
|||||||
{
|
{
|
||||||
return CardLookup.GetValueOrDefault(cardName);
|
return CardLookup.GetValueOrDefault(cardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="mana-curve">
|
<div class="mana-curve">
|
||||||
@foreach (var entry in Distribution)
|
@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-container">
|
||||||
<div class="mana-bar-label">@entry.Count</div>
|
<div class="mana-bar-label">@entry.Count</div>
|
||||||
<div class="mana-bar" style="height: @($"{Math.Max(height, 5)}%")"></div>
|
<div class="mana-bar" style="height: @($"{Math.Max(height, 5)}%")"></div>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@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;
|
[Parameter] public int MaxCostLabel { get; set; } = 10;
|
||||||
|
|
||||||
private int MaxCount => Distribution.Count > 0 ? Distribution.Max(x => x.Count) : 0;
|
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",
|
Title = "Lifeblood",
|
||||||
Category = "Faction",
|
Category = "Faction",
|
||||||
Content = "Concepts:\r\n- Wide Boards\r\n- Overpower",
|
Content = "Concepts:\r\n\r\n- Wide Boards\r\n- Overpower",
|
||||||
Frontmatter = new()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "category", "Faction" },
|
{ "category", "Faction" },
|
||||||
@@ -164,7 +164,7 @@ public static class DocDatabase
|
|||||||
{
|
{
|
||||||
Title = "Phasetide",
|
Title = "Phasetide",
|
||||||
Category = "Faction",
|
Category = "Faction",
|
||||||
Content = "Concepts:\r\n- Healing\r\n-",
|
Content = "Concepts:\r\n\r\n- Healing\r\n-",
|
||||||
Frontmatter = new()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "category", "Faction" },
|
{ "category", "Faction" },
|
||||||
@@ -174,7 +174,7 @@ public static class DocDatabase
|
|||||||
{
|
{
|
||||||
Title = "Silence",
|
Title = "Silence",
|
||||||
Category = "Faction",
|
Category = "Faction",
|
||||||
Content = "Concepts:\r\n- Activatable Agents\r\n-",
|
Content = "Concepts:\r\n\r\n- Activatable Agents\r\n-",
|
||||||
Frontmatter = new()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "category", "Faction" },
|
{ "category", "Faction" },
|
||||||
@@ -184,7 +184,7 @@ public static class DocDatabase
|
|||||||
{
|
{
|
||||||
Title = "Singularity",
|
Title = "Singularity",
|
||||||
Category = "Faction",
|
Category = "Faction",
|
||||||
Content = "Concepts:\r\n- Discard",
|
Content = "Concepts:\r\n\r\n- Discard",
|
||||||
Frontmatter = new()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "category", "Faction" },
|
{ "category", "Faction" },
|
||||||
@@ -204,7 +204,7 @@ public static class DocDatabase
|
|||||||
{
|
{
|
||||||
Title = "Sungrace",
|
Title = "Sungrace",
|
||||||
Category = "Faction",
|
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()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "category", "Faction" },
|
{ "category", "Faction" },
|
||||||
@@ -479,7 +479,7 @@ public static class DocDatabase
|
|||||||
{
|
{
|
||||||
Title = "Onboarding Track",
|
Title = "Onboarding Track",
|
||||||
Category = "Pass",
|
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()
|
Frontmatter = new()
|
||||||
{
|
{
|
||||||
{ "expPer", "30" },
|
{ "expPer", "30" },
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<NavigationTracker/>
|
||||||
<article class="content px-4">
|
<article class="content px-4">
|
||||||
<TelerikRootComponent>
|
<TelerikRootComponent>
|
||||||
@Body
|
@Body
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
<PageTitle>Chrono CCG - Agents</PageTitle>
|
<PageTitle>Chrono CCG - Agents</PageTitle>
|
||||||
|
|
||||||
<HeadContent>
|
<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>
|
</HeadContent>
|
||||||
|
|
||||||
<div class="agents-page">
|
<div class="agents-page">
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
<PageTitle>Chrono CCG - @(deck?.Name ?? "Deck Details")</PageTitle>
|
<PageTitle>Chrono CCG - @(deck?.Name ?? "Deck Details")</PageTitle>
|
||||||
|
|
||||||
<HeadContent>
|
<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>
|
</HeadContent>
|
||||||
|
|
||||||
<div class="deck-detail-page">
|
<div class="deck-detail-page">
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
<article class="deck-article">
|
<article class="deck-article">
|
||||||
<header class="deck-header">
|
<header class="deck-header">
|
||||||
<h1>@deck.Name</h1>
|
<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)
|
@if (deck.Factions.Count > 0)
|
||||||
{
|
{
|
||||||
<div class="deck-factions">
|
<div class="deck-factions">
|
||||||
@@ -124,7 +126,7 @@
|
|||||||
{
|
{
|
||||||
if (deck == null) return [];
|
if (deck == null) return [];
|
||||||
var counts = new Dictionary<int, int>();
|
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)
|
foreach (var cardName in deck.Cards)
|
||||||
{
|
{
|
||||||
@@ -139,4 +141,5 @@
|
|||||||
|
|
||||||
return counts.OrderBy(x => x.Key).Select(x => (x.Key, x.Value)).ToList();
|
return counts.OrderBy(x => x.Key).Select(x => (x.Key, x.Value)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
<PageTitle>Chrono CCG - Decks</PageTitle>
|
<PageTitle>Chrono CCG - Decks</PageTitle>
|
||||||
|
|
||||||
<HeadContent>
|
<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>
|
</HeadContent>
|
||||||
|
|
||||||
<div class="decks-page">
|
<div class="decks-page">
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
<div class="decks-header-row">
|
<div class="decks-header-row">
|
||||||
<p class="text-secondary">@decks.Count deck@(decks.Count != 1 ? "s" : "")</p>
|
<p class="text-secondary">@decks.Count deck@(decks.Count != 1 ? "s" : "")</p>
|
||||||
<div class="form-check form-switch">
|
<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>
|
<label class="form-check-label" for="showPrecons">Show Precons</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +72,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<DeckData> decks = [];
|
private List<DeckData> decks = [];
|
||||||
private bool showPrecons = false;
|
private bool showPrecons;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -83,8 +85,8 @@
|
|||||||
.Where(d => d.IsVisible)
|
.Where(d => d.IsVisible)
|
||||||
.Where(d =>
|
.Where(d =>
|
||||||
showPrecons
|
showPrecons
|
||||||
? d.DeckType is "precon" or "store" :
|
? d.DeckType is "precon" or "store"
|
||||||
d.DeckType is "custom")
|
: d.DeckType is "custom")
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="category-list">
|
<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
|
All Pages
|
||||||
</div>
|
</div>
|
||||||
@foreach (var category in categories)
|
@foreach (var category in categories)
|
||||||
{
|
{
|
||||||
var cat = category;
|
var cat = category;
|
||||||
<div class="category-item @(selectedCategory == cat ? "active" : "")" @onclick="() => SelectCategory(cat)">
|
<div class="category-item @(selectedCategory == cat ? "active" : "")"
|
||||||
|
@onclick="() => SelectCategory(cat)">
|
||||||
@cat
|
@cat
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -134,7 +136,9 @@
|
|||||||
return doc.Frontmatter.TryGetValue(key, out var val) && !string.IsNullOrWhiteSpace(val) ? val : null;
|
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",
|
"category" => "Category",
|
||||||
"description" => "Description",
|
"description" => "Description",
|
||||||
@@ -148,6 +152,8 @@
|
|||||||
"faction" => "Faction",
|
"faction" => "Faction",
|
||||||
"exp" => "EXP Reward",
|
"exp" => "EXP Reward",
|
||||||
"see" => "See Also",
|
"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 Chrono.Model;
|
||||||
using System.Text.RegularExpressions;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace Shared.Services;
|
namespace Shared.Services;
|
||||||
|
|
||||||
public class CardRenderingService
|
public class CardRenderingService
|
||||||
{
|
{
|
||||||
private readonly List<string> _cardNames;
|
|
||||||
private readonly Dictionary<string, CardData> _cardLookup;
|
private readonly Dictionary<string, CardData> _cardLookup;
|
||||||
|
private readonly List<string> _cardNames;
|
||||||
private readonly Regex _nameRegex;
|
private readonly Regex _nameRegex;
|
||||||
|
|
||||||
public CardRenderingService()
|
public CardRenderingService()
|
||||||
@@ -33,7 +33,7 @@ public class CardRenderingService
|
|||||||
{
|
{
|
||||||
return builder =>
|
return builder =>
|
||||||
{
|
{
|
||||||
int sequence = 0;
|
var sequence = 0;
|
||||||
var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
foreach (var paragraph in paragraphs)
|
foreach (var paragraph in paragraphs)
|
||||||
@@ -41,31 +41,27 @@ public class CardRenderingService
|
|||||||
builder.OpenElement(sequence++, "p");
|
builder.OpenElement(sequence++, "p");
|
||||||
|
|
||||||
var matches = _nameRegex.Matches(paragraph);
|
var matches = _nameRegex.Matches(paragraph);
|
||||||
int lastIndex = 0;
|
var lastIndex = 0;
|
||||||
|
|
||||||
foreach (Match match in matches)
|
foreach (Match match in matches)
|
||||||
{
|
{
|
||||||
if (match.Index > lastIndex)
|
if (match.Index > lastIndex)
|
||||||
{
|
|
||||||
builder.AddContent(sequence++, paragraph.Substring(lastIndex, match.Index - lastIndex));
|
builder.AddContent(sequence++, paragraph.Substring(lastIndex, match.Index - lastIndex));
|
||||||
}
|
|
||||||
|
|
||||||
var cardName = match.Value;
|
var cardName = match.Value;
|
||||||
var card = LookupCard(cardName);
|
var card = LookupCard(cardName);
|
||||||
|
|
||||||
builder.OpenElement(sequence++, "button");
|
builder.OpenElement(sequence++, "button");
|
||||||
builder.AddAttribute(sequence++, "class", "inline-card-btn");
|
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.AddContent(sequence++, cardName);
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
|
|
||||||
lastIndex = match.Index + match.Length;
|
lastIndex = match.Index + match.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastIndex < paragraph.Length)
|
if (lastIndex < paragraph.Length) builder.AddContent(sequence++, paragraph.Substring(lastIndex));
|
||||||
{
|
|
||||||
builder.AddContent(sequence++, paragraph.Substring(lastIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.CloseElement();
|
builder.CloseElement();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Playwright;
|
using Microsoft.Playwright.NUnit;
|
||||||
using Microsoft.Playwright.NUnit;
|
|
||||||
using Tests.PageObjects;
|
using Tests.PageObjects;
|
||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
@@ -8,10 +7,6 @@ namespace Tests;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class FeatureTests : PageTest
|
public class FeatureTests : PageTest
|
||||||
{
|
{
|
||||||
private DecksPage _decksPage;
|
|
||||||
private DeckDetailPage _deckDetailPage;
|
|
||||||
private AgentsPage _agentsPage;
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
@@ -20,6 +15,10 @@ public class FeatureTests : PageTest
|
|||||||
_agentsPage = new AgentsPage(Page);
|
_agentsPage = new AgentsPage(Page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DecksPage _decksPage;
|
||||||
|
private DeckDetailPage _deckDetailPage;
|
||||||
|
private AgentsPage _agentsPage;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task DeckManaCurve_ShouldBeVisibleOnDetail()
|
public async Task DeckManaCurve_ShouldBeVisibleOnDetail()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ namespace Tests.PageObjects;
|
|||||||
|
|
||||||
public class AgentsPage : BasePage
|
public class AgentsPage : BasePage
|
||||||
{
|
{
|
||||||
public AgentsPage(IPage page) : base(page) { }
|
public AgentsPage(IPage page) : base(page)
|
||||||
|
{
|
||||||
public async Task GotoAsync() => await NavigateToAsync("/agents");
|
}
|
||||||
|
|
||||||
public ILocator Grid => Page.Locator(".agents-grid");
|
public ILocator Grid => Page.Locator(".agents-grid");
|
||||||
|
|
||||||
|
public async Task GotoAsync()
|
||||||
|
{
|
||||||
|
await NavigateToAsync("/agents");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task WaitForGridAsync()
|
public async Task WaitForGridAsync()
|
||||||
{
|
{
|
||||||
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ namespace Tests.PageObjects;
|
|||||||
|
|
||||||
public abstract class BasePage
|
public abstract class BasePage
|
||||||
{
|
{
|
||||||
protected readonly IPage Page;
|
|
||||||
protected readonly string BaseUrl = "http://localhost:5256";
|
protected readonly string BaseUrl = "http://localhost:5256";
|
||||||
|
protected readonly IPage Page;
|
||||||
|
|
||||||
protected BasePage(IPage page)
|
protected BasePage(IPage page)
|
||||||
{
|
{
|
||||||
@@ -17,5 +17,8 @@ public abstract class BasePage
|
|||||||
await Page.GotoAsync($"{BaseUrl}{path}");
|
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 class DeckDetailPage : BasePage
|
||||||
{
|
{
|
||||||
public DeckDetailPage(IPage page) : base(page) { }
|
public DeckDetailPage(IPage page) : base(page)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public ILocator ManaCurve => Page.Locator(".mana-curve");
|
public ILocator ManaCurve => Page.Locator(".mana-curve");
|
||||||
public ILocator ManaBars => Page.Locator(".mana-bar");
|
public ILocator ManaBars => Page.Locator(".mana-bar");
|
||||||
public ILocator ManaBarLabels => Page.Locator(".mana-bar-label");
|
public ILocator ManaBarLabels => Page.Locator(".mana-bar-label");
|
||||||
public ILocator ManaCosts => Page.Locator(".mana-cost");
|
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 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)
|
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;
|
||||||
using Microsoft.Playwright.NUnit;
|
using Microsoft.Playwright.NUnit;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ public class PlaywrightTests : PageTest
|
|||||||
public async Task HomePage_ShouldLoad()
|
public async Task HomePage_ShouldLoad()
|
||||||
{
|
{
|
||||||
await Page.GotoAsync(BaseUrl);
|
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
|
// Check for some content that should be on the home page
|
||||||
var heading = Page.Locator("h1");
|
var heading = Page.Locator("h1");
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace Tests;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TelerikLicenseTests : PageTest
|
public class TelerikLicenseTests : PageTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private const string BaseUrl = "http://localhost:5256";
|
private const string BaseUrl = "http://localhost:5256";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
+12
-17
@@ -1,13 +1,12 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
[SetUpFixture]
|
[SetUpFixture]
|
||||||
public class TestSetup
|
public class TestSetup
|
||||||
{
|
{
|
||||||
private Process? _serverProcess;
|
|
||||||
private const string ServerUrl = "http://localhost:5256";
|
private const string ServerUrl = "http://localhost:5256";
|
||||||
|
private Process? _serverProcess;
|
||||||
|
|
||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
public async Task BeforeAllTests()
|
public async Task BeforeAllTests()
|
||||||
@@ -17,14 +16,9 @@ public class TestSetup
|
|||||||
// Find solution root
|
// Find solution root
|
||||||
var currentDir = AppContext.BaseDirectory;
|
var currentDir = AppContext.BaseDirectory;
|
||||||
while (currentDir != null && !File.Exists(Path.Combine(currentDir, "Chrono.sln")))
|
while (currentDir != null && !File.Exists(Path.Combine(currentDir, "Chrono.sln")))
|
||||||
{
|
|
||||||
currentDir = Path.GetDirectoryName(currentDir);
|
currentDir = Path.GetDirectoryName(currentDir);
|
||||||
}
|
|
||||||
|
|
||||||
if (currentDir == null)
|
if (currentDir == null) throw new Exception("Could not find Chrono.sln to determine project path.");
|
||||||
{
|
|
||||||
throw new Exception("Could not find Chrono.sln to determine project path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var projectPath = Path.Combine(currentDir, "Server", "Server.csproj");
|
var projectPath = Path.Combine(currentDir, "Server", "Server.csproj");
|
||||||
|
|
||||||
@@ -41,24 +35,26 @@ public class TestSetup
|
|||||||
|
|
||||||
_serverProcess = Process.Start(startInfo);
|
_serverProcess = Process.Start(startInfo);
|
||||||
|
|
||||||
_serverProcess.OutputDataReceived += (s, e) => { if (e.Data != null) Console.WriteLine($"[SERVER OUT] {e.Data}"); };
|
_serverProcess.OutputDataReceived += (s, e) =>
|
||||||
_serverProcess.ErrorDataReceived += (s, e) => { if (e.Data != null) Console.WriteLine($"[SERVER ERR] {e.Data}"); };
|
{
|
||||||
|
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.BeginOutputReadLine();
|
||||||
_serverProcess.BeginErrorReadLine();
|
_serverProcess.BeginErrorReadLine();
|
||||||
|
|
||||||
if (_serverProcess == null)
|
if (_serverProcess == null) throw new Exception("Failed to start server process.");
|
||||||
{
|
|
||||||
throw new Exception("Failed to start server process.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the server to be ready
|
// Wait for the server to be ready
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var timeout = TimeSpan.FromSeconds(30);
|
var timeout = TimeSpan.FromSeconds(30);
|
||||||
bool isReady = false;
|
var isReady = false;
|
||||||
|
|
||||||
while (sw.Elapsed < timeout)
|
while (sw.Elapsed < timeout)
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await client.GetAsync(ServerUrl);
|
var response = await client.GetAsync(ServerUrl);
|
||||||
@@ -73,7 +69,6 @@ public class TestSetup
|
|||||||
// Wait and retry
|
// Wait and retry
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!isReady)
|
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">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@using System.Text.RegularExpressions
|
||||||
@namespace Chrono.Components
|
@namespace Chrono.Components
|
||||||
|
|
||||||
@if (Card != null)
|
@if (Card != null)
|
||||||
@@ -88,7 +89,8 @@
|
|||||||
var target = LookupCard(targetName);
|
var target = LookupCard(targetName);
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
<button class="inline-card-btn" @onclick="() => Navigate(target)">@targetName</button>
|
<button class="inline-card-btn"
|
||||||
|
@onclick="() => Navigate(target)">@targetName</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -108,7 +110,8 @@
|
|||||||
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
var fromCard = LookupCard(Card.ImmortalizeFrom);
|
||||||
if (fromCard != null)
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -181,7 +184,9 @@
|
|||||||
{
|
{
|
||||||
await Http.PostAsJsonAsync("api/notes", new CardNote { CardName = Card.Name, Note = currentNote });
|
await Http.PostAsJsonAsync("api/notes", new CardNote { CardName = Card.Name, Note = currentNote });
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
@@ -209,7 +214,9 @@
|
|||||||
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RenderFragment RenderDescription(string description) => builder =>
|
private RenderFragment RenderDescription(string description)
|
||||||
|
{
|
||||||
|
return builder =>
|
||||||
{
|
{
|
||||||
var cardNames = CardDatabase.Cards
|
var cardNames = CardDatabase.Cards
|
||||||
.Select(c => c.Name)
|
.Select(c => c.Name)
|
||||||
@@ -217,18 +224,18 @@
|
|||||||
.OrderByDescending(n => n.Length)
|
.OrderByDescending(n => n.Length)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
int seq = 0;
|
var seq = 0;
|
||||||
var remaining = description;
|
var remaining = description;
|
||||||
|
|
||||||
while (!string.IsNullOrEmpty(remaining))
|
while (!string.IsNullOrEmpty(remaining))
|
||||||
{
|
{
|
||||||
int bestIdx = -1;
|
var bestIdx = -1;
|
||||||
string? bestName = null;
|
string? bestName = null;
|
||||||
|
|
||||||
foreach (var name in cardNames)
|
foreach (var name in cardNames)
|
||||||
{
|
{
|
||||||
var pattern = $@"\b{System.Text.RegularExpressions.Regex.Escape(name)}\b";
|
var pattern = $@"\b{Regex.Escape(name)}\b";
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(remaining, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
var match = Regex.Match(remaining, pattern, RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
@@ -270,3 +277,5 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-detail {
|
.card-detail {
|
||||||
@@ -231,7 +235,8 @@
|
|||||||
text-decoration-color: #7b73ff;
|
text-decoration-color: #7b73ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@media (max-width: 768px) {
|
@
|
||||||
|
@media (max-width: 768px) {
|
||||||
.detail-layout {
|
.detail-layout {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/cards"
|
@page "/cards"
|
||||||
@inject HttpClient Http
|
@inject HttpClient Http
|
||||||
|
@inject AnalyticsService AnalyticsService
|
||||||
|
|
||||||
<PageTitle>Chrono CCG - Card Gallery</PageTitle>
|
<PageTitle>Chrono CCG - Card Gallery</PageTitle>
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@
|
|||||||
<button class="tab agent @(categoryFilter == "Agent" ? "active" : "")" @onclick='@(() => SetCategory("Agent"))'>
|
<button class="tab agent @(categoryFilter == "Agent" ? "active" : "")" @onclick='@(() => SetCategory("Agent"))'>
|
||||||
<i class="bi bi-person-fill"></i> Agents
|
<i class="bi bi-person-fill"></i> Agents
|
||||||
</button>
|
</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
|
<i class="bi bi-person-fill"></i> Immortalized
|
||||||
</button>
|
</button>
|
||||||
<!-- //Todo make agent being linked to immortal for side by side sorting
|
<!-- //Todo make agent being linked to immortal for side by side sorting
|
||||||
@@ -234,11 +236,13 @@
|
|||||||
private void SetCategory(string cat)
|
private void SetCategory(string cat)
|
||||||
{
|
{
|
||||||
categoryFilter = categoryFilter == cat ? "" : cat;
|
categoryFilter = categoryFilter == cat ? "" : cat;
|
||||||
|
_ = AnalyticsService.TrackEvent("filter_category", new { category = categoryFilter });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearCategoryFilter()
|
private void ClearCategoryFilter()
|
||||||
{
|
{
|
||||||
categoryFilter = "";
|
categoryFilter = "";
|
||||||
|
_ = AnalyticsService.TrackEvent("filter_category", new { category = "All" });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearFactionFilter()
|
private void ClearFactionFilter()
|
||||||
@@ -264,6 +268,7 @@
|
|||||||
private void SelectCard(CardData card)
|
private void SelectCard(CardData card)
|
||||||
{
|
{
|
||||||
selectedCard = card;
|
selectedCard = card;
|
||||||
|
_ = AnalyticsService.TrackEvent("select_item", new { item_name = card.Name, item_category = card.Category });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseDetail()
|
private void CloseDetail()
|
||||||
@@ -278,4 +283,5 @@
|
|||||||
factionFilter = "";
|
factionFilter = "";
|
||||||
costFilter = "";
|
costFilter = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
using Shared.Services;
|
||||||
using Web;
|
using Web;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
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.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
builder.Services.AddTelerikBlazor();
|
builder.Services.AddTelerikBlazor();
|
||||||
builder.Services.AddSingleton<Shared.Services.CardRenderingService>();
|
builder.Services.AddSingleton<CardRenderingService>();
|
||||||
|
builder.Services.AddScoped<AnalyticsService>();
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Shared.Layout
|
@using Shared.Layout
|
||||||
@using Shared.Pages
|
@using Shared.Pages
|
||||||
|
@using Shared.Services
|
||||||
|
@using Shared.Components
|
||||||
@using Chrono.Components
|
@using Chrono.Components
|
||||||
@using Telerik.Blazor
|
@using Telerik.Blazor
|
||||||
@using Telerik.Blazor.Components
|
@using Telerik.Blazor.Components
|
||||||
|
|||||||
@@ -257,9 +257,6 @@ a, .btn-link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.inline-card-btn {
|
.inline-card-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -6,26 +6,29 @@
|
|||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>Chrono CCG - Card Gallery</title>
|
<title>Chrono CCG - Card Gallery</title>
|
||||||
<base href="/"/>
|
<base href="/"/>
|
||||||
<meta name="description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
<meta 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"/>
|
name="description"/>
|
||||||
|
<meta content="Chrono CCG, Reference, Card Gallery, DB" name="keywords"/>
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website"/>
|
<meta content="website" property="og:type"/>
|
||||||
<meta property="og:url" content="https://chronoccg.com/"/>
|
<meta content="https://chronoccg.com/" property="og:url"/>
|
||||||
<meta property="og:title" content="Chrono CCG - Card Gallery"/>
|
<meta content="Chrono CCG - Card Gallery" property="og:title"/>
|
||||||
<meta property="og:description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
<meta 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"/>
|
property="og:description"/>
|
||||||
|
<meta content="https://chronoccg.com/favicon.png" property="og:image"/>
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta property="twitter:card" content="summary_large_image"/>
|
<meta content="summary_large_image" property="twitter:card"/>
|
||||||
<meta property="twitter:url" content="https://chronoccg.com/"/>
|
<meta content="https://chronoccg.com/" property="twitter:url"/>
|
||||||
<meta property="twitter:title" content="Chrono CCG - Card Gallery"/>
|
<meta content="Chrono CCG - Card Gallery" property="twitter:title"/>
|
||||||
<meta property="twitter:description" content="Explore the full collection of Chrono CCG cards. Filter by cost, faction, and type."/>
|
<meta 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"/>
|
property="twitter:description"/>
|
||||||
|
<meta content="https://chronoccg.com/favicon.png" property="twitter:image"/>
|
||||||
|
|
||||||
<link href="manifest.json" rel="manifest"/>
|
<link href="manifest.json" rel="manifest"/>
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
<link href="icon-512.png" rel="apple-touch-icon" sizes="512x512"/>
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
<link href="icon-192.png" rel="apple-touch-icon" sizes="192x192"/>
|
||||||
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||||
<link crossorigin href="https://fonts.gstatic.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="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>
|
<script defer src="/_content/Telerik.UI.for.Blazor/js/telerik-blazor.js"></script>
|
||||||
<link href="/favicon.png" rel="icon" type="image/png"/>
|
<link href="/favicon.png" rel="icon" type="image/png"/>
|
||||||
<script type="importmap"></script>
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
// In development, always fetch from the network and do not cache.
|
// 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.
|
// 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": {
|
"navigationFallback": {
|
||||||
"rewrite": "/index.html",
|
"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]]"
|
imageLink: "[[Aardvark Precinct Captain.png]]"
|
||||||
ranking: A
|
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]]
|
![[Aardvark Precinct Captain.png]]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ archetypes:
|
|||||||
- "[[Rewound]]"
|
- "[[Rewound]]"
|
||||||
imageLink: "[[Divergence Assassin.png]]"
|
imageLink: "[[Divergence Assassin.png]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
![[Divergence Assassin.png]]
|
![[Divergence Assassin.png]]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ archetypes:
|
|||||||
imageLink: "[[Scuttling Spares.png]]"
|
imageLink: "[[Scuttling Spares.png]]"
|
||||||
rank: S
|
rank: S
|
||||||
---
|
---
|
||||||
|
|
||||||
I haven't played this card, but it sounds like it obviously enables discard.
|
I haven't played this card, but it sounds like it obviously enables discard.
|
||||||
|
|
||||||
![[Scuttling Spares.png]]
|
![[Scuttling Spares.png]]
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
category: Debuff
|
category: Debuff
|
||||||
description: Set this Agent's strength to 0 until Round End.
|
description: Set this Agent's strength to 0 until Round End.
|
||||||
---
|
---
|
||||||
|
|
||||||
Reduce attack to zero.
|
Reduce attack to zero.
|
||||||
@@ -74,20 +74,30 @@ factions:
|
|||||||
deckCode: AUHUE2LHEBCW4ZLSM54SAQ3POB4QWY3PNZZXI4TVMN2GKZBTAADACASPCICESCQCAUFDCAYGAMCAGAQDDYBQCAYBAOTQCAYQAMDQG
|
deckCode: AUHUE2LHEBCW4ZLSM54SAQ3POB4QWY3PNZZXI4TVMN2GKZBTAADACASPCICESCQCAUFDCAYGAMCAGAQDDYBQCAYBAOTQCAYQAMDQG
|
||||||
deckType: custom
|
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.
|
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.
|
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
|
deckCode: AUMU65TFOJTGY33XNFXGOICTMVRXK4TJOR4SAQ3POB4QWY3PNZZXI4TVMN2GKZBRACQAAANZAEBZEAI6AQFTSAYCAMOQGBADAEBQCAZ2AMCAGGQDAQBQCAY
|
||||||
deckType: custom
|
deckType: custom
|
||||||
---
|
---
|
||||||
|
|
||||||
Built deck as a joke, but it kind of feels nuts in limited testing.
|
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.
|
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]].
|
If you survive, start scaling your board with [[Awakened Security System]] or [[Limit Breaker]].
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
category: Faction
|
category: Faction
|
||||||
---
|
---
|
||||||
|
|
||||||
Concepts:
|
Concepts:
|
||||||
|
|
||||||
- Wide Boards
|
- Wide Boards
|
||||||
- Overpower
|
- Overpower
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
category: Faction
|
category: Faction
|
||||||
---
|
---
|
||||||
|
|
||||||
Concepts:
|
Concepts:
|
||||||
|
|
||||||
- Healing
|
- Healing
|
||||||
-
|
-
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
category: Faction
|
category: Faction
|
||||||
---
|
---
|
||||||
|
|
||||||
Concepts:
|
Concepts:
|
||||||
|
|
||||||
- Activatable Agents
|
- Activatable Agents
|
||||||
-
|
-
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
---
|
---
|
||||||
category: Faction
|
category: Faction
|
||||||
---
|
---
|
||||||
|
|
||||||
Concepts:
|
Concepts:
|
||||||
|
|
||||||
- Discard
|
- Discard
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
---
|
---
|
||||||
category: Faction
|
category: Faction
|
||||||
---
|
---
|
||||||
|
|
||||||
# Energy Reserves
|
# 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:
|
Concepts:
|
||||||
|
|
||||||
- Ramp
|
- Ramp
|
||||||
- Using and Replenishing Reserved Energy
|
- Using and Replenishing Reserved Energy
|
||||||
-
|
-
|
||||||
@@ -17,4 +17,5 @@ archetypes:
|
|||||||
- "[[Keyword/Sprout]]"
|
- "[[Keyword/Sprout]]"
|
||||||
imageLink: "[[Horticulturist Oswald.png]]"
|
imageLink: "[[Horticulturist Oswald.png]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
![[Horticulturist Oswald.png]]
|
![[Horticulturist Oswald.png]]
|
||||||
@@ -17,4 +17,5 @@ archetypes:
|
|||||||
- "[[Rewound]]"
|
- "[[Rewound]]"
|
||||||
imageLink: "[[Violet Inquisitioner]]"
|
imageLink: "[[Violet Inquisitioner]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
![[Violent Inquisitioner.png]]
|
![[Violent Inquisitioner.png]]
|
||||||
@@ -17,4 +17,5 @@ archetypes:
|
|||||||
- N/A
|
- N/A
|
||||||
imageLink: "[[Scoot Sparkles.png]]"
|
imageLink: "[[Scoot Sparkles.png]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
![[Scoot Sparkles.png]]
|
![[Scoot Sparkles.png]]
|
||||||
@@ -17,4 +17,5 @@ archetypes:
|
|||||||
- N/A
|
- N/A
|
||||||
imageLink: "[[The Ripper.png]]"
|
imageLink: "[[The Ripper.png]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
![[The Ripper.png]]
|
![[The Ripper.png]]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ category: Pass
|
|||||||
---
|
---
|
||||||
|
|
||||||
| Tier | Reward |
|
| Tier | Reward |
|
||||||
| ---- | -------------------------------- |
|
|------|----------------------------------|
|
||||||
| 1 | Sprouts Record |
|
| 1 | Sprouts Record |
|
||||||
| 2 | 120 Core Charges |
|
| 2 | 120 Core Charges |
|
||||||
| 3 | Burn Precon |
|
| 3 | Burn Precon |
|
||||||
@@ -18,5 +18,5 @@ category: Pass
|
|||||||
| 10 | 240 Core Charges, 1 Draft Ticket |
|
| 10 | 240 Core Charges, 1 Draft Ticket |
|
||||||
|
|
||||||
| Core Charges | Flux |
|
| Core Charges | Flux |
|
||||||
| ------------ | ---- |
|
|--------------|------|
|
||||||
| 660 | 75 |
|
| 660 | 75 |
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
category: Quest
|
category: Quest
|
||||||
---
|
---
|
||||||
|
|
||||||
1: 30cc 30xp
|
1: 30cc 30xp
|
||||||
2: 20cc 25xp
|
2: 20cc 25xp
|
||||||
3: 20cc 20xp
|
3: 20cc 20xp
|
||||||
|
|||||||
Reference in New Issue
Block a user