Vibe Google Analytics

This commit is contained in:
2026-06-25 18:55:04 -04:00
parent fdf8e754f5
commit 73ae3eac53
86 changed files with 53925 additions and 46039 deletions
View File
View File
+1
View File
@@ -0,0 +1 @@
[]
+1
View File
@@ -0,0 +1 @@
3.0
View File
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\Model\Model.csproj"/>
<ProjectReference Include="..\Shared\Shared.csproj"/>
</ItemGroup>
</Project>
@@ -21,20 +21,20 @@ main {
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
@@ -90,9 +90,9 @@ main {
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -17,7 +17,7 @@
.top-row {
min-height: 3.5rem;
background-color: rgba(0,0,0,0.4);
background-color: rgba(0, 0, 0, 0.4);
}
.navbar-brand {
@@ -51,33 +51,33 @@
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
background-color: rgba(255, 255, 255, 0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
background-color: rgba(255, 255, 255, 0.1);
color: white;
}
@@ -31,12 +31,11 @@
opacity: 0;
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
&[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;
}
&[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;
}
}
@@ -96,13 +95,13 @@
border-radius: 4px;
}
#components-reconnect-modal button:hover {
background-color: #3b6ea2;
}
#components-reconnect-modal button:hover {
background-color: #3b6ea2;
}
#components-reconnect-modal button:active {
background-color: #6b9ed2;
}
#components-reconnect-modal button:active {
background-color: #6b9ed2;
}
.components-rejoining-animation {
position: relative;
@@ -110,17 +109,17 @@
height: 80px;
}
.components-rejoining-animation div {
position: absolute;
border: 3px solid #0087ff;
opacity: 1;
border-radius: 50%;
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.components-rejoining-animation div {
position: absolute;
border: 3px solid #0087ff;
opacity: 1;
border-radius: 50%;
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.components-rejoining-animation div:nth-child(2) {
animation-delay: -0.5s;
}
.components-rejoining-animation div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes components-rejoining-animation {
0% {
+1 -1
View File
@@ -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()
{
+7 -3
View File
@@ -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;
}
}
+4 -2
View File
@@ -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>
+1 -1
View File
@@ -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();
}
+18 -18
View File
@@ -1,23 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5230",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7086;http://localhost:5230",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5230",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7086;http://localhost:5230",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+4 -4
View File
@@ -13,7 +13,7 @@ a, .btn-link {
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
@@ -42,9 +42,9 @@ h1:focus {
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+339 -327
View File
@@ -5,427 +5,435 @@
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
margin-bottom: 0;
}
dt {
font-weight: 700;
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
font-weight: bolder;
}
small {
font-size: 0.875em;
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
bottom: -0.25em;
}
sup {
top: -0.5em;
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
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);
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
@@ -434,21 +442,21 @@ tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
display: inline-block;
}
button {
border-radius: 0;
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
outline: 0;
}
input,
@@ -456,76 +464,80 @@ button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
text-transform: none;
}
[role=button] {
cursor: pointer;
cursor: pointer;
}
select {
word-wrap: normal;
word-wrap: normal;
}
select:disabled {
opacity: 1;
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
@@ -535,16 +547,16 @@ legend + * {
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
-webkit-appearance: textfield;
outline-offset: -2px;
}
/* rtl:raw:
@@ -556,42 +568,42 @@ legend + * {
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
display: inline-block;
}
iframe {
border: 0;
border: 0;
}
summary {
display: list-item;
cursor: pointer;
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
vertical-align: baseline;
}
[hidden] {
display: none !important;
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */
@@ -5,427 +5,435 @@
*/
:root,
[data-bs-theme=light] {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
--bs-orange: #fd7e14;
--bs-yellow: #ffc107;
--bs-green: #198754;
--bs-teal: #20c997;
--bs-cyan: #0dcaf0;
--bs-black: #000;
--bs-white: #fff;
--bs-gray: #6c757d;
--bs-gray-dark: #343a40;
--bs-gray-100: #f8f9fa;
--bs-gray-200: #e9ecef;
--bs-gray-300: #dee2e6;
--bs-gray-400: #ced4da;
--bs-gray-500: #adb5bd;
--bs-gray-600: #6c757d;
--bs-gray-700: #495057;
--bs-gray-800: #343a40;
--bs-gray-900: #212529;
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
--bs-success: #198754;
--bs-info: #0dcaf0;
--bs-warning: #ffc107;
--bs-danger: #dc3545;
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 13, 110, 253;
--bs-secondary-rgb: 108, 117, 125;
--bs-success-rgb: 25, 135, 84;
--bs-info-rgb: 13, 202, 240;
--bs-warning-rgb: 255, 193, 7;
--bs-danger-rgb: 220, 53, 69;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #052c65;
--bs-secondary-text-emphasis: #2b2f32;
--bs-success-text-emphasis: #0a3622;
--bs-info-text-emphasis: #055160;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #58151c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #cfe2ff;
--bs-secondary-bg-subtle: #e2e3e5;
--bs-success-bg-subtle: #d1e7dd;
--bs-info-bg-subtle: #cff4fc;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #f8d7da;
--bs-light-bg-subtle: #fcfcfd;
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #9ec5fe;
--bs-secondary-border-subtle: #c4c8cb;
--bs-success-border-subtle: #a3cfbb;
--bs-info-border-subtle: #9eeaf9;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #f1aeb5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
--bs-body-font-family: var(--bs-font-sans-serif);
--bs-body-font-size: 1rem;
--bs-body-font-weight: 400;
--bs-body-line-height: 1.5;
--bs-body-color: #212529;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg: #fff;
--bs-body-bg-rgb: 255, 255, 255;
--bs-emphasis-color: #000;
--bs-emphasis-color-rgb: 0, 0, 0;
--bs-secondary-color: rgba(33, 37, 41, 0.75);
--bs-secondary-color-rgb: 33, 37, 41;
--bs-secondary-bg: #e9ecef;
--bs-secondary-bg-rgb: 233, 236, 239;
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
--bs-tertiary-color-rgb: 33, 37, 41;
--bs-tertiary-bg: #f8f9fa;
--bs-tertiary-bg-rgb: 248, 249, 250;
--bs-heading-color: inherit;
--bs-link-color: #0d6efd;
--bs-link-color-rgb: 13, 110, 253;
--bs-link-decoration: underline;
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
--bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
--bs-border-radius: 0.375rem;
--bs-border-radius-sm: 0.25rem;
--bs-border-radius-lg: 0.5rem;
--bs-border-radius-xl: 1rem;
--bs-border-radius-xxl: 2rem;
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
--bs-border-radius-pill: 50rem;
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
--bs-focus-ring-width: 0.25rem;
--bs-focus-ring-opacity: 0.25;
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
--bs-form-valid-color: #198754;
--bs-form-valid-border-color: #198754;
--bs-form-invalid-color: #dc3545;
--bs-form-invalid-border-color: #dc3545;
}
[data-bs-theme=dark] {
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
color-scheme: dark;
--bs-body-color: #dee2e6;
--bs-body-color-rgb: 222, 226, 230;
--bs-body-bg: #212529;
--bs-body-bg-rgb: 33, 37, 41;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: rgba(222, 226, 230, 0.75);
--bs-secondary-color-rgb: 222, 226, 230;
--bs-secondary-bg: #343a40;
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #084298;
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
}
*,
*::before,
*::after {
box-sizing: border-box;
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
margin: 1rem 0;
color: inherit;
border: 0;
border-top: var(--bs-border-width) solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
color: var(--bs-heading-color);
}
h1 {
font-size: calc(1.375rem + 1.5vw);
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
margin-bottom: 0;
}
dt {
font-weight: 700;
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
font-weight: bolder;
}
small {
font-size: 0.875em;
font-size: 0.875em;
}
mark {
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
padding: 0.1875em;
color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
bottom: -0.25em;
}
sup {
top: -0.5em;
top: -0.5em;
}
a {
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
text-decoration: underline;
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);
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: var(--bs-secondary-color);
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
@@ -434,21 +442,21 @@ tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
display: inline-block;
}
button {
border-radius: 0;
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
outline: 0;
}
input,
@@ -456,76 +464,80 @@ button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
text-transform: none;
}
[role=button] {
cursor: pointer;
cursor: pointer;
}
select {
word-wrap: normal;
word-wrap: normal;
}
select:disabled {
opacity: 1;
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
@@ -535,60 +547,62 @@ legend + * {
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
display: inline-block;
}
iframe {
border: 0;
border: 0;
}
summary {
display: list-item;
cursor: pointer;
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
vertical-align: baseline;
}
[hidden] {
display: none !important;
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
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
+1 -1
View File
@@ -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; } = [];
}
+2 -2
View File
@@ -20,8 +20,8 @@
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<Routes/>
<script src="_framework/blazor.web.js"></script>
</body>
</html>
+1 -1
View File
@@ -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"/>
+7 -7
View File
@@ -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();
-3
View File
@@ -257,9 +257,6 @@ a, .btn-link {
}
.inline-card-btn {
background: none;
border: none;
+14 -6
View File
@@ -1,5 +1,4 @@
@namespace Shared.Components
@using Chrono.Model
@if (Card != null)
{
@@ -11,7 +10,7 @@
{
<div class="detail-image">
<img src="@Card.ImagePath" alt="@Card.Name"
onerror="this.style.display='none';this.parentElement.style.display='none'" />
onerror="this.style.display='none';this.parentElement.style.display='none'"/>
</div>
}
<div class="detail-info">
@@ -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 {
+6 -3
View File
@@ -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);
}
}
+2 -2
View File
@@ -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;
}
}
+8 -8
View File
@@ -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" },
@@ -194,7 +194,7 @@ public static class DocDatabase
{
Title = "Splintergleam",
Category = "Faction",
Content = "# Self Damage\r\n\r\nCan hurt it's own units for power. Has [[Bleed]] to make reoccurring damage on units very easy.\r\n\r\n# Core Damage \r\n\r\nCan hurt both cores easily. Has [[Breakdown]] to take advantage of having an injured core for more damage.",
Content = "# Self Damage\r\n\r\nCan hurt it's own units for power. Has [[Bleed]] to make reoccurring damage on units very easy.\r\n\r\n# Core Damage\r\n\r\nCan hurt both cores easily. Has [[Breakdown]] to take advantage of having an injured core for more damage.",
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" },
@@ -491,7 +491,7 @@ public static class DocDatabase
{
Title = "Daily Wins",
Category = "Quest",
Content = "1: 30cc 30xp \n2: 20cc 25xp \n3: 20cc 20xp \n4: 10cc 20xp \n5: 10cc 15xp \n6-12 are all simply 5xp. \n\nSo the daily total win reward for 12 wins is 90cc/145xp.",
Content = "1: 30cc 30xp\n2: 20cc 25xp\n3: 20cc 20xp\n4: 10cc 20xp\n5: 10cc 15xp\n6-12 are all simply 5xp.\n\nSo the daily total win reward for 12 wins is 90cc/145xp.",
Frontmatter = new()
{
{ "category", "Quest" },
+1
View File
@@ -6,6 +6,7 @@
</div>
<main>
<NavigationTracker/>
<article class="content px-4">
<TelerikRootComponent>
@Body
+2 -1
View File
@@ -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">
+11 -8
View File
@@ -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">
@@ -44,14 +46,14 @@
}
</div>
<ManaCurve Distribution="GetManaDistribution()" MaxCostLabel="10" />
<ManaCurve Distribution="GetManaDistribution()" MaxCostLabel="10"/>
</header>
@if (deck.Keycards.Count > 0)
{
<section class="deck-section">
<h2><i class="bi bi-star-fill" style="color: var(--gold);"></i> Keycards</h2>
<CardGrid Cards="deck.Keycards" OnSelectCard="SelectCard" />
<CardGrid Cards="deck.Keycards" OnSelectCard="SelectCard"/>
</section>
}
@@ -59,13 +61,13 @@
{
<section class="deck-section">
<h2><i class="bi bi-shuffle"></i> Divers</h2>
<CardGrid Cards="deck.Divers" OnSelectCard="SelectCard" />
<CardGrid Cards="deck.Divers" OnSelectCard="SelectCard"/>
</section>
}
<section class="deck-section">
<h2><i class="bi bi-collection-fill"></i> Cards</h2>
<CardGrid Cards="deck.Cards" OnSelectCard="SelectCard" />
<CardGrid Cards="deck.Cards" OnSelectCard="SelectCard"/>
</section>
@if (deck.Description != null)
@@ -84,7 +86,7 @@
<CardDetailModal Card="selectedCard"
OnClose="CloseDetail"
OnSelectCard="SelectCard"
DescriptionRenderer="@(desc => CardRenderer.RenderDescription(desc, SelectCard))" />
DescriptionRenderer="@(desc => CardRenderer.RenderDescription(desc, SelectCard))"/>
@code {
[Parameter] public string Name { get; set; } = "";
@@ -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();
}
}
+7 -5
View File
@@ -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();
}
+24 -18
View File
@@ -13,17 +13,19 @@
<aside class="docs-sidebar">
<div class="search-box">
<i class="bi bi-search"></i>
<input type="text" placeholder="Search docs..." @bind="searchQuery" @bind:event="oninput" />
<input type="text" placeholder="Search docs..." @bind="searchQuery" @bind:event="oninput"/>
</div>
<div class="category-list">
<div class="category-item @(selectedCategory == null ? "active" : "")" @onclick="() => SelectCategory(null)">
<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,20 +136,24 @@
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)
{
"category" => "Category",
"description" => "Description",
"essencePrice" => "Essence Price",
"crytalPrice" => "Crystal Price",
"coreChargesPrice" => "Core Charges Price",
"pricePerPack" => "Price per Pack",
"codeCharges" => "Code Charges",
"coreCharges" => "Core Charges",
"currency" => "Currency",
"faction" => "Faction",
"exp" => "EXP Reward",
"see" => "See Also",
_ => string.Concat(key.Select((c, i) => i > 0 && char.IsUpper(c) ? " " + c : c.ToString())),
};
return key switch
{
"category" => "Category",
"description" => "Description",
"essencePrice" => "Essence Price",
"crytalPrice" => "Crystal Price",
"coreChargesPrice" => "Core Charges Price",
"pricePerPack" => "Price per Pack",
"codeCharges" => "Code Charges",
"coreCharges" => "Core Charges",
"currency" => "Currency",
"faction" => "Faction",
"exp" => "EXP Reward",
"see" => "See Also",
_ => 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);
}
}
+8 -12
View File
@@ -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();
}
+5 -6
View File
@@ -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()
{
+8 -3
View File
@@ -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);
+5 -2
View File
@@ -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");
}
}
+7 -2
View File
@@ -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();
}
}
+13 -4
View File
@@ -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();
}
}
+2 -1
View File
@@ -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");
-1
View File
@@ -7,7 +7,6 @@ namespace Tests;
[TestFixture]
public class TelerikLicenseTests : PageTest
{
private const string BaseUrl = "http://localhost:5256";
[Test]
+12 -17
View File
@@ -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)
{
+2 -1
View File
@@ -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"/>
+57 -48
View File
@@ -1,3 +1,4 @@
@using System.Text.RegularExpressions
@namespace Chrono.Components
@if (Card != null)
@@ -10,7 +11,7 @@
{
<div class="detail-image">
<img src="@Card.ImagePath" alt="@Card.Name"
onerror="this.style.display='none';this.parentElement.style.display='none'" />
onerror="this.style.display='none';this.parentElement.style.display='none'"/>
</div>
}
<div class="detail-info">
@@ -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,64 +214,68 @@
string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase));
}
private RenderFragment RenderDescription(string description) => builder =>
private RenderFragment RenderDescription(string description)
{
var cardNames = CardDatabase.Cards
.Select(c => c.Name)
.Where(n => !string.IsNullOrWhiteSpace(n))
.OrderByDescending(n => n.Length)
.ToList();
int seq = 0;
var remaining = description;
while (!string.IsNullOrEmpty(remaining))
return builder =>
{
int bestIdx = -1;
string? bestName = null;
var cardNames = CardDatabase.Cards
.Select(c => c.Name)
.Where(n => !string.IsNullOrWhiteSpace(n))
.OrderByDescending(n => n.Length)
.ToList();
foreach (var name in cardNames)
var seq = 0;
var remaining = description;
while (!string.IsNullOrEmpty(remaining))
{
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 bestIdx = -1;
string? bestName = null;
if (match.Success)
foreach (var name in cardNames)
{
if (bestIdx == -1 || match.Index < bestIdx)
var pattern = $@"\b{Regex.Escape(name)}\b";
var match = Regex.Match(remaining, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
bestIdx = match.Index;
bestName = match.Value;
if (bestIdx == -1 || match.Index < bestIdx)
{
bestIdx = match.Index;
bestName = match.Value;
}
}
}
}
if (bestIdx != -1 && bestName != null)
{
if (bestIdx > 0)
builder.AddContent(seq++, remaining[..bestIdx]);
var card = LookupCard(bestName);
if (card != null)
if (bestIdx != -1 && bestName != null)
{
builder.OpenElement(seq++, "button");
builder.AddAttribute(seq++, "class", "inline-card-btn");
builder.AddAttribute(seq++, "onclick",
EventCallback.Factory.Create(this, () => Navigate(card)));
builder.AddContent(seq++, bestName);
builder.CloseElement();
if (bestIdx > 0)
builder.AddContent(seq++, remaining[..bestIdx]);
var card = LookupCard(bestName);
if (card != null)
{
builder.OpenElement(seq++, "button");
builder.AddAttribute(seq++, "class", "inline-card-btn");
builder.AddAttribute(seq++, "onclick",
EventCallback.Factory.Create(this, () => Navigate(card)));
builder.AddContent(seq++, bestName);
builder.CloseElement();
}
else
{
builder.AddContent(seq++, bestName);
}
remaining = remaining[(bestIdx + bestName.Length)..];
}
else
{
builder.AddContent(seq++, bestName);
builder.AddContent(seq++, remaining);
remaining = "";
}
}
};
}
remaining = remaining[(bestIdx + bestName.Length)..];
}
else
{
builder.AddContent(seq++, remaining);
remaining = "";
}
}
};
}
+8 -3
View File
@@ -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;
+15 -9
View File
@@ -1,10 +1,11 @@
@page "/cards"
@inject HttpClient Http
@inject AnalyticsService AnalyticsService
<PageTitle>Chrono CCG - Card Gallery</PageTitle>
<HeadContent>
<meta name="description" content="Browse and filter all Chrono CCG cards. Explore agents, spells, and more." />
<meta name="description" content="Browse and filter all Chrono CCG cards. Explore agents, spells, and more."/>
</HeadContent>
<div class="gallery-container">
@@ -21,14 +22,15 @@
<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
<button class="tab agent @(agentLink ? "active" : "")" @onclick='@(() => ToggleAgentLink())'>
<i class="bi bi-person-fill"></i> Link
</button>
-->
<!-- //Todo make agent being linked to immortal for side by side sorting
<button class="tab agent @(agentLink ? "active" : "")" @onclick='@(() => ToggleAgentLink())'>
<i class="bi bi-person-fill"></i> Link
</button>
-->
<button class="tab spell @(categoryFilter == "Spell" ? "active" : "")" @onclick='@(() => SetCategory("Spell"))'>
<i class="bi bi-wand"></i> Spells
</button>
@@ -118,7 +120,7 @@
{
<div class="card-grid @(showDetailedView ? "detailed-view" : "")">
@{ var idx = 0; }
@foreach (var card in filteredCards.Where(a=>a.Category is "Agent" or "Spell" or "Immortalized"))
@foreach (var card in filteredCards.Where(a => a.Category is "Agent" or "Spell" or "Immortalized"))
{
<div class="card-cell @(selectedCard == card ? "selected" : "")"
style="--i: @idx"
@@ -171,7 +173,7 @@
@if (selectedCard != null)
{
<CardDialog Card="selectedCard" OnClose="CloseDetail" OnNavigate="SelectCard" />
<CardDialog Card="selectedCard" OnClose="CloseDetail" OnNavigate="SelectCard"/>
}
@code {
@@ -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 = "";
}
}
+3 -1
View File
@@ -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();
+2 -2
View File
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/>
</ItemGroup>
<ItemGroup>
@@ -21,6 +21,6 @@
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj"/>
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj"/>
</ItemGroup>
</Project>
+2
View File
@@ -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
-3
View File
@@ -257,9 +257,6 @@ a, .btn-link {
}
.inline-card-btn {
background: none;
border: none;
+38 -15
View File
@@ -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="manifest.json" rel="manifest"/>
<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>
+2 -1
View File
@@ -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', () => {
});
@@ -8,8 +8,8 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/];
const offlineAssetsExclude = [/^service-worker\.js$/];
async function onInstall(event) {
console.info('Service worker installing');
@@ -18,7 +18,7 @@ async function onInstall(event) {
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
.map(asset => new Request(asset.url, {integrity: asset.hash, cache: 'no-cache'}));
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
+7 -1
View File
@@ -1,6 +1,12 @@
{
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/css/*", "/lib/*", "/_framework/*", "/_content/*", "/sample-data/*"]
"exclude": [
"/css/*",
"/lib/*",
"/_framework/*",
"/_content/*",
"/sample-data/*"
]
}
}
@@ -1,10 +1,10 @@
{
"id": "frontmatter-folder-organizer",
"name": "Frontmatter Folder Organizer",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"description": "Organize markdown files into folders based on frontmatter hierarchy.",
"author": "Obsidian",
"authorUrl": "https://obsidian.md",
"isDesktopOnly": false
"id": "frontmatter-folder-organizer",
"name": "Frontmatter Folder Organizer",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"description": "Organize markdown files into folders based on frontmatter hierarchy.",
"author": "Obsidian",
"authorUrl": "https://obsidian.md",
"isDesktopOnly": false
}
@@ -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]]
+1
View File
@@ -2,4 +2,5 @@
category: Debuff
description: Set this Agent's strength to 0 until Round End.
---
Reduce attack to zero.
+17 -7
View File
@@ -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.
+3 -1
View File
@@ -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]].
+2
View File
@@ -1,6 +1,8 @@
---
category: Faction
---
Concepts:
- Wide Boards
- Overpower
+2
View File
@@ -1,6 +1,8 @@
---
category: Faction
---
Concepts:
- Healing
-
+2
View File
@@ -1,6 +1,8 @@
---
category: Faction
---
Concepts:
- Activatable Agents
-
+2
View File
@@ -1,5 +1,7 @@
---
category: Faction
---
Concepts:
- Discard
+4 -1
View File
@@ -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]]
+2 -2
View File
@@ -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
View File
@@ -1,6 +1,7 @@
---
category: Quest
---
1: 30cc 30xp
2: 20cc 25xp
3: 20cc 20xp