Tech stack stub page and changing project to be just one Web Assembly project for now
This commit is contained in:
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
3.0
|
||||
@@ -1,5 +1,4 @@
|
||||
<Solution>
|
||||
<Project Path="AOW4.Client/AOW4.Client.csproj"/>
|
||||
<Project Path="AOW4.SeleniumTests/AOW4.SeleniumTests.csproj"/>
|
||||
<Project Path="AOW4/AOW4.csproj"/>
|
||||
<Project Path="SeleniumTests/SeleniumTests.csproj" />
|
||||
<Project Path="WebAssembly/WebAssembly.csproj" />
|
||||
</Solution>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AOW4.Client\AOW4.Client.csproj"/>
|
||||
<PackageReference Include="MudBlazor" Version="9.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.8"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
@using AOW4.Portals
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
@@ -20,3 +21,57 @@
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<ToastPortal/>
|
||||
<ConfirmationDialogPortal/>
|
||||
<TechStackPortal/>
|
||||
|
||||
|
||||
<style>
|
||||
a {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #8fc5ff;
|
||||
text-decoration-thickness: 3px;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--severity-warning-color: #2a2000;
|
||||
--severity-warning-border-color: #755c13;
|
||||
--severity-error-color: #290102;
|
||||
--severity-error-border-color: #4C2C33;
|
||||
--severity-information-color: #030129;
|
||||
--severity-information-border-color: #2c3a4c;
|
||||
--severity-success-color: #042901;
|
||||
--severity-success-border-color: #2E4C2C;
|
||||
|
||||
--accent: #432462;
|
||||
--primary: #4308a3;
|
||||
--primary-border: #2c0b62;
|
||||
--primary-hover: #5e00f7;
|
||||
--primary-border-hover: #a168ff;
|
||||
--background: #161618;
|
||||
--secondary: #23133e;
|
||||
--secondary-hover: #2a0070;
|
||||
--secondary-border-hover: #a168ff;
|
||||
--paper: #252526;
|
||||
--paper-border: #151516;
|
||||
|
||||
--paper-hover: #52366f;
|
||||
--paper-border-hover: #653497;
|
||||
|
||||
--info: #451376;
|
||||
--info-border: #210b36;
|
||||
|
||||
--dialog-border-color: black;
|
||||
--dialog-border-width: 2px;
|
||||
--dialog-radius: 6px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
@using AOW4.Components.Inputs
|
||||
@implements IDisposable;
|
||||
@inject IMyDialogService MyDialogService
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (MyDialogService.IsVisible)
|
||||
{
|
||||
<div class="confirmDialogBackground" onclick="@CloseDialog">
|
||||
<div class="confirmDialogContainer"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true">
|
||||
|
||||
<div class="confirmDialogHeader">
|
||||
@MyDialogService.GetDialogContents().Title
|
||||
</div>
|
||||
<div class="confirmDialogBody">
|
||||
@MyDialogService.GetDialogContents().Message
|
||||
</div>
|
||||
|
||||
<div class="confirmDialogFooter">
|
||||
<ButtonComponent MyButtonType="MyButtonType.Secondary"
|
||||
OnClick="MyDialogService.GetDialogContents().OnCancel">
|
||||
Cancel
|
||||
</ButtonComponent>
|
||||
<ButtonComponent MyButtonType="MyButtonType.Primary"
|
||||
OnClick="MyDialogService.GetDialogContents().OnConfirm">
|
||||
@MyDialogService.GetDialogContents().ConfirmButtonLabel
|
||||
</ButtonComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pageContents * {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.confirmDialogBackground {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.confirmDialogContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
|
||||
/**
|
||||
background-color: var(--background);
|
||||
border-width: var(--dialog-border-width);
|
||||
border-style: solid;
|
||||
border-color: var(--dialog-border-color);
|
||||
border-radius: var(--dialog-radius);
|
||||
*/
|
||||
padding: 8px;
|
||||
|
||||
|
||||
box-shadow: 1px 2px 2px black;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.confirmDialogHeader {
|
||||
font-size: 1.4em;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.confirmDialogBody {
|
||||
padding: 12px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.confirmDialogFooter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
MyDialogService.Subscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
|
||||
public void CloseDialog()
|
||||
{
|
||||
MyDialogService.Hide();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
@using AOW4.Components.Inputs
|
||||
@implements IDisposable;
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
@if (MyDialogService.IsVisible && MyDialogService.GetDialogContents().TechStack != null)
|
||||
{
|
||||
var techStack = MyDialogService.GetDialogContents().TechStack;
|
||||
|
||||
<div class="techStackDialogBackground" onclick="@CloseDialog">
|
||||
<div class="techStackDialogContainer"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true">
|
||||
|
||||
<div class="techStackDialogHeader">
|
||||
@techStack.Name
|
||||
</div>
|
||||
<div class="techStackDialogBody">
|
||||
<div class="description">
|
||||
@techStack.Description
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="extendedNotes">
|
||||
@((MarkupString)(techStack.ExtendedNotes?.Replace("\n", "<br />") ?? ""))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="techStackDialogFooter">
|
||||
<ButtonComponent MyButtonType="MyButtonType.Primary"
|
||||
OnClick="CloseDialog">
|
||||
Close
|
||||
</ButtonComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pageContents * {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.techStackDialogBackground {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.techStackDialogContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
width: 800px;
|
||||
max-height: 80vh;
|
||||
background-color: var(--paper);
|
||||
border-width: var(--dialog-border-width);
|
||||
border-style: solid;
|
||||
border-color: var(--dialog-border-color);
|
||||
border-radius: var(--dialog-radius);
|
||||
padding: 16px;
|
||||
box-shadow: 1px 2px 2px black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.techStackDialogHeader {
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--paper-border);
|
||||
}
|
||||
|
||||
.techStackDialogBody {
|
||||
padding: 16px 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-style: italic;
|
||||
margin-bottom: 16px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.extendedNotes {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.techStackDialogFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--paper-border);
|
||||
}
|
||||
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(StateHasChanged);
|
||||
|
||||
Console.WriteLine("TechStackDialogComponent initialized");
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
public void CloseDialog()
|
||||
{
|
||||
Console.WriteLine( "Closing dialog");
|
||||
MyDialogService.Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
@inject IToastService ToastService
|
||||
|
||||
@using AOW4.Data
|
||||
@implements IDisposable
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
@if (Toast == null)
|
||||
{
|
||||
<div>Add toast object...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div onclick="@Dismiss" style="opacity: @Opacity()" class="toastContainer @Toast.SeverityType.ToLower()">
|
||||
<div class="toastTitle">
|
||||
@Toast.Title
|
||||
</div>
|
||||
<div>
|
||||
@Toast.Message
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<style>
|
||||
.toastContainer {
|
||||
border: 4px solid;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-items: stretch;
|
||||
width: 250px;
|
||||
cursor: pointer;
|
||||
}
|
||||
/**
|
||||
.@SeverityType.Warning.ToLower() {
|
||||
background-color: var(--severity-warning-color);
|
||||
border-color: var(--severity-warning-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Error.ToLower() {
|
||||
background-color: var(--severity-error-color);
|
||||
border-color: var(--severity-error-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Information.ToLower() {
|
||||
background-color: var(--severity-information-color);
|
||||
border-color: var(--severity-information-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Success.ToLower() {
|
||||
background-color: var(--severity-success-color);
|
||||
border-color: var(--severity-success-border-color);
|
||||
}*/
|
||||
|
||||
.toastTitle {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public ToastModel? Toast { get; set; }
|
||||
|
||||
private readonly float removalTime = 1300;
|
||||
private readonly float fadeoutTime = 1200;
|
||||
|
||||
private float Opacity()
|
||||
{
|
||||
if (Toast!.Age < fadeoutTime)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1.0f - (Toast.Age - fadeoutTime) / (removalTime - fadeoutTime);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
ToastService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void Dismiss()
|
||||
{
|
||||
ToastService.RemoveToast(Toast!);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
ToastService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
if (Toast!.Age > removalTime)
|
||||
{
|
||||
ToastService.RemoveToast(Toast);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<button class="buttonContainer @MyButtonType.ToString().ToLower()" @onclick="ButtonClicked">@ChildContent</button>
|
||||
|
||||
<style>
|
||||
.buttonContainer {
|
||||
padding: 16px;
|
||||
border: 1px solid;
|
||||
border-radius: 8px;
|
||||
font-weight: 800;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.@(MyButtonType.Primary.ToString().ToLower()) {
|
||||
border-color: var(--primary);
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.@MyButtonType.Secondary.ToString().ToLower() {
|
||||
border-color: var(--secondary);
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.@MyButtonType.Primary.ToString().ToLower():hover {
|
||||
background-color: var(--primary-hover);
|
||||
border-color: var(--primary-border-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.@MyButtonType.Secondary.ToString().ToLower():hover {
|
||||
background-color: var(--secondary-hover);
|
||||
border-color: var(--secondary-border-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
|
||||
|
||||
[Parameter] public EventCallback<EventArgs> OnClick { get; set; }
|
||||
|
||||
[Parameter] public MyButtonType MyButtonType { get; set; }
|
||||
|
||||
private void ButtonClicked(EventArgs eventArgs)
|
||||
{
|
||||
OnClick.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AOW4.Components.Inputs;
|
||||
|
||||
public enum MyButtonType
|
||||
{
|
||||
Primary, // Positive Actions
|
||||
Secondary // Destruction Action
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using MudBlazor
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudPopoverProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<div class="page">
|
||||
<main>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
Add("Relations with Free Cities and Rulers", material.IncreaseRelationWithFreeCitiesAndRulers);
|
||||
Add("Combat Casting Points", material.IncreaseCombatCastingPoints);
|
||||
Add("World Map Casting Points", material.IncreaseWorldCastingPoints);
|
||||
Add("HP Regeneration", material.IncreaseHPRegen);
|
||||
Add("HP Regeneration", material.IncreaseHpRegen);
|
||||
Add("Hit Points", material.IncreaseHitPoints);
|
||||
Add("Experience Percent", material.IncreaseExperiencePercent);
|
||||
Add("Allegiance from Umbral Dwellings", material.IncreaseAllegianceFromUmbralDwellings);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
@page "/tech-stack"
|
||||
@using AOW4.Services
|
||||
@using MudBlazor
|
||||
@rendermode InteractiveWebAssembly
|
||||
@inject IMyDialogService DialogService
|
||||
|
||||
<div class="techStackPage">
|
||||
<h1>Tech Stack</h1>
|
||||
|
||||
<div class="techStackGrid">
|
||||
@foreach (var tech in Data.TechStackData.RawData)
|
||||
{
|
||||
<div class="techStackItem @(tech.InUse ? "" : "notInUse")" @onclick="() => OpenDialog(tech)">
|
||||
<div class="techStackName">@tech.Name</div>
|
||||
<div class="techStackDescription">@tech.Description</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.techStackPage {
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.techStackGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.techStackItem {
|
||||
background-color: var(--paper);
|
||||
border: 1px solid var(--paper-border);
|
||||
border-radius: var(--dialog-radius);
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.techStackItem:hover {
|
||||
background-color: var(--paper-hover);
|
||||
border-color: var(--paper-border-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.techStackItem.notInUse {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.techStackName {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.techStackDescription {
|
||||
font-size: 1em;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private void OpenDialog(AOW4.Data.TechStack tech)
|
||||
{
|
||||
Console.WriteLine(tech.Name);
|
||||
|
||||
DialogService.Show(new DialogContents
|
||||
{
|
||||
Title = tech.Name,
|
||||
TechStack = tech
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,3 +10,9 @@
|
||||
@using AOW4.Client
|
||||
@using AOW4.Components
|
||||
@using AOW4.Components.Layout
|
||||
|
||||
|
||||
|
||||
@using System.Globalization
|
||||
@using System.Reflection
|
||||
@using System.Timers
|
||||
@@ -30,7 +30,7 @@ public class MagicMaterial
|
||||
public int? IncreaseRelationWithFreeCitiesAndRulers { get; set; }
|
||||
public int? IncreaseCombatCastingPoints { get; set; }
|
||||
public int? IncreaseWorldCastingPoints { get; set; }
|
||||
public int? IncreaseHPRegen { get; set; }
|
||||
public int? IncreaseHpRegen { get; set; }
|
||||
public int? IncreaseHitPoints { get; set; }
|
||||
public int? IncreaseExperiencePercent { get; set; }
|
||||
public int? IncreaseAllegianceFromUmbralDwellings { get; set; }
|
||||
|
||||
@@ -102,7 +102,7 @@ public static class MagicMaterialsData
|
||||
Name = "Blood Glass",
|
||||
Description = "Sunless Terrain only. Counts as Ore.",
|
||||
IncreaseDraft = 20,
|
||||
IncreaseHPRegen = 5,
|
||||
IncreaseHpRegen = 5,
|
||||
GlobalBonus = "+5 HP regeneration (on the world map)",
|
||||
InfusionEffects1 = """
|
||||
Greater Inflict Bleed
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AOW4.Data;
|
||||
|
||||
public class SearchPointModel
|
||||
{
|
||||
public string Title { get; set; } = "";
|
||||
public string Summary { get; set; } = "";
|
||||
|
||||
public string Tags { get; set; } = "";
|
||||
public string PointType { get; set; } = "";
|
||||
public string Href { get; set; } = "";
|
||||
}
|
||||
+33
-19
@@ -4,52 +4,66 @@ public static class SectionsData
|
||||
{
|
||||
public static List<Section> GetAllSections()
|
||||
{
|
||||
return new List<Section>
|
||||
{
|
||||
new()
|
||||
return
|
||||
[
|
||||
new Section
|
||||
{
|
||||
Name = "About",
|
||||
Description = "Meta information on this website",
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Tech Stack",
|
||||
Url = "/tech-stack",
|
||||
Description = "Information about the technology stack that will be used in this project."
|
||||
}
|
||||
]
|
||||
},
|
||||
new Section
|
||||
{
|
||||
Name = "Calculators",
|
||||
Description = "Useful calculator tools for various computations",
|
||||
Links = new List<SectionLink>
|
||||
{
|
||||
new()
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Building Plan Calculator",
|
||||
Url = "/building-calculator",
|
||||
Description = "Simulate build order timing and gold income over turns."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
new()
|
||||
|
||||
new Section
|
||||
{
|
||||
Name = "References",
|
||||
Description = "Reference materials and documentation",
|
||||
Links = new List<SectionLink>
|
||||
{
|
||||
new()
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Magic Materials Reference",
|
||||
Url = "/references/magic-materials",
|
||||
Description = "View the magic material dataset and bonuses in a reference table."
|
||||
},
|
||||
new()
|
||||
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Province Improvements Reference",
|
||||
Url = "/references/province-improvements",
|
||||
Description =
|
||||
"View the province improvements dataset including costs, effects, and requirements."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
new()
|
||||
|
||||
new Section
|
||||
{
|
||||
Name = "Learning",
|
||||
Description = "Educational resources and learning materials",
|
||||
Links = new List<SectionLink>
|
||||
{
|
||||
// Add learning links here in the future
|
||||
}
|
||||
Links = []
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AOW4.Data;
|
||||
|
||||
public class SeverityType
|
||||
{
|
||||
public static string Warning = "Warning";
|
||||
public static string Information = "Information";
|
||||
public static string Error = "Error";
|
||||
public static string Success = "Success";
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace AOW4.Data;
|
||||
|
||||
public class TechStack
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string? ExtendedNotes { get; set; }
|
||||
|
||||
public bool InUse { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace AOW4.Data;
|
||||
|
||||
public static class TechStackData
|
||||
{
|
||||
public static List<TechStack> RawData =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "Blazor WebAssembly",
|
||||
Description = "Framework for web applications for easy distribution and no hosting costs with third parties",
|
||||
InUse = true,
|
||||
ExtendedNotes = """
|
||||
Simple and easy to distribute. I like C#, and it's C# web development, what's not to like.
|
||||
Obviously con is if I want the user to save state between usages, I am relying on something unreliable like local storage which can be cleared by the user or obviously not transferred between different browsers.
|
||||
So needs to be used in reference and informational only content. I suppose I could rely on the user to handle copying and pasting the data, but a tad cumbersome and unrealistic to expect that.
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Blazor Server",
|
||||
Description = "Framework for web applications that allows for database interactions",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
Easy to distribute. I'll need to have to deal with authentication and all those security concerns.
|
||||
Needs a database to store data. Local hosting Postgresql is the plan.
|
||||
Unideal support implications. Need to make deleting all user data very easy for legal compliance.
|
||||
Less reliability. Server can go down, and I can have a long outage. Fallback to Blazor WebAssembly version hosted by a third party.
|
||||
"""
|
||||
},
|
||||
new() {
|
||||
Name = "MAUI",
|
||||
Description = "Framework for mobile and desktop applications",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
So I get around the whole unreliableness of web storage by saving files locally.
|
||||
I can easily distribute via an exe file or apk. Obviously con of that is the user needs to install a exe file or apk, so there is going be a clear 'this could be a virus' type of warning popup to discourage the user.
|
||||
Potentially can distribute via Google Play or Windows Store and be subject to the whims and veto power of a third party.
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "PostgreSQL",
|
||||
Description = "Relational database management system",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
I am picking PostgreSQL because it appears free and simple. I have pgAdmin installed and it running.
|
||||
Need to just actually implement using it in the far future.
|
||||
"""
|
||||
},
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AOW4.Data;
|
||||
|
||||
public class ToastModel
|
||||
{
|
||||
public string Title { get; set; } = "addTitle";
|
||||
public string Message { get; set; } = "addMessage";
|
||||
public string SeverityType { get; set; } = "addType";
|
||||
public float Age { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
using AOW4.Data;
|
||||
using AOW4.Services;
|
||||
|
||||
namespace AOW4;
|
||||
|
||||
public interface IToastService
|
||||
{
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
void AddToast(ToastModel toast);
|
||||
void RemoveToast(ToastModel toast);
|
||||
bool HasToasts();
|
||||
List<ToastModel> GetToasts();
|
||||
void AgeToasts();
|
||||
void ClearAllToasts();
|
||||
}
|
||||
|
||||
|
||||
public interface ISearchService
|
||||
{
|
||||
public List<SearchPointModel> SearchPoints { get; set; }
|
||||
|
||||
public Dictionary<string, List<SearchPointModel>> Searches { get; set; }
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
|
||||
public void Search(string entityId);
|
||||
|
||||
public Task Load();
|
||||
|
||||
public bool IsLoaded();
|
||||
void Show();
|
||||
void Hide();
|
||||
}
|
||||
|
||||
public interface IMyDialogService
|
||||
{
|
||||
public bool IsVisible { get; set; }
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
public void Show(DialogContents dialogContents);
|
||||
public DialogContents GetDialogContents();
|
||||
public void Hide();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@using AOW4.Components.Dialog
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
<ConfirmationDialogComponent></ConfirmationDialogComponent>
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@using AOW4.Components.Dialog
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
<TechStackDialogComponent></TechStackDialogComponent>
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@using System.Timers
|
||||
@using AOW4.Components.Feedback
|
||||
@using AOW4.Data
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IToastService ToastService
|
||||
|
||||
@if (ToastService.HasToasts())
|
||||
{
|
||||
<div class="toastsContainer">
|
||||
@foreach (var toast in Toasts)
|
||||
{
|
||||
<ToastComponent Toast="toast"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<style>
|
||||
.toastsContainer {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
right: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@code {
|
||||
private List<ToastModel> Toasts => ToastService.GetToasts();
|
||||
|
||||
private Timer _ageTimer = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
ToastService.Subscribe(OnUpdate);
|
||||
|
||||
_ageTimer = new Timer(10);
|
||||
_ageTimer.Elapsed += OnAge!;
|
||||
_ageTimer.Enabled = true;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
ToastService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
|
||||
void OnAge(object? sender, ElapsedEventArgs elapsedEventArgs)
|
||||
{
|
||||
ToastService.AgeToasts();
|
||||
_ageTimer.Enabled = true;
|
||||
}
|
||||
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using AOW4;
|
||||
using AOW4.Components;
|
||||
using AOW4.Services;
|
||||
using MudBlazor.Services;
|
||||
using _Imports = AOW4.Client._Imports;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -7,6 +10,13 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveWebAssemblyComponents();
|
||||
|
||||
builder.Services.AddScoped<IToastService, ToastService>();
|
||||
builder.Services.AddScoped<IMyDialogService, MyDialogService>();
|
||||
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AOW4.Services;
|
||||
|
||||
public class DialogContents
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string ConfirmButtonLabel { get; set; }
|
||||
public EventCallback<EventArgs> OnConfirm { get; set; }
|
||||
public EventCallback<EventArgs> OnCancel { get; set; }
|
||||
public AOW4.Data.TechStack? TechStack { get; set; }
|
||||
}
|
||||
|
||||
public class MyDialogService : IMyDialogService
|
||||
{
|
||||
private DialogContents _dialogContents = new();
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Show(DialogContents dialogContents)
|
||||
{
|
||||
_dialogContents = dialogContents;
|
||||
IsVisible = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public DialogContents GetDialogContents()
|
||||
{
|
||||
return _dialogContents;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
IsVisible = false;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using AOW4.Data;
|
||||
|
||||
namespace AOW4.Services;
|
||||
|
||||
public class SearchService : ISearchService
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
public List<SearchPointModel> SearchPoints { get; set; } = new();
|
||||
|
||||
public Dictionary<string, List<SearchPointModel>> Searches { get; set; } = new();
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Search(string entityId)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task Load()
|
||||
{
|
||||
Searches.Add("MagicMaterial", []);
|
||||
|
||||
foreach (var entity in MagicMaterialsData.RawData)
|
||||
{
|
||||
var title = entity.Name;
|
||||
var description = entity.Description ?? "";
|
||||
|
||||
var summary =
|
||||
|
||||
description.Length > 35
|
||||
? description[..30].Trim() + "..."
|
||||
: description.Length > 0
|
||||
? description
|
||||
: "";
|
||||
|
||||
SearchPoints.Add(new SearchPointModel
|
||||
{
|
||||
Title = title,
|
||||
Tags = "Magic Material",
|
||||
PointType = "Entity",
|
||||
Summary = summary,
|
||||
Href = ""// Add a link to the entity page
|
||||
});
|
||||
|
||||
Searches["MagicMaterial"].Add(SearchPoints.Last());
|
||||
}
|
||||
|
||||
_isLoaded = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public bool IsLoaded()
|
||||
{
|
||||
return _isLoaded;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
IsVisible = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
IsVisible = false;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using AOW4.Data;
|
||||
|
||||
namespace AOW4.Services;
|
||||
|
||||
public class ToastService : IToastService
|
||||
{
|
||||
private readonly List<ToastModel> _toasts = [];
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void AddToast(ToastModel toast)
|
||||
{
|
||||
_toasts.Insert(0, toast);
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void RemoveToast(ToastModel toast)
|
||||
{
|
||||
_toasts.Remove(toast);
|
||||
}
|
||||
|
||||
public bool HasToasts()
|
||||
{
|
||||
return _toasts.Count > 0;
|
||||
}
|
||||
|
||||
public List<ToastModel> GetToasts()
|
||||
{
|
||||
return _toasts;
|
||||
}
|
||||
|
||||
public void AgeToasts()
|
||||
{
|
||||
foreach (var toast in _toasts) toast.Age++;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void ClearAllToasts()
|
||||
{
|
||||
_toasts.Clear();
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<IsPackable>false</IsPackable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>AOW4.SeleniumTests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -0,0 +1,61 @@
|
||||
@using WebAssembly.Portals
|
||||
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
</Found>
|
||||
</Router>
|
||||
|
||||
<ToastPortal/>
|
||||
<ConfirmationDialogPortal/>
|
||||
<TechStackPortal/>
|
||||
|
||||
|
||||
<style>
|
||||
a {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #8fc5ff;
|
||||
text-decoration-thickness: 3px;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--severity-warning-color: #2a2000;
|
||||
--severity-warning-border-color: #755c13;
|
||||
--severity-error-color: #290102;
|
||||
--severity-error-border-color: #4C2C33;
|
||||
--severity-information-color: #030129;
|
||||
--severity-information-border-color: #2c3a4c;
|
||||
--severity-success-color: #042901;
|
||||
--severity-success-border-color: #2E4C2C;
|
||||
|
||||
--accent: #432462;
|
||||
--primary: #4308a3;
|
||||
--primary-border: #2c0b62;
|
||||
--primary-hover: #5e00f7;
|
||||
--primary-border-hover: #a168ff;
|
||||
--background: #161618;
|
||||
--secondary: #23133e;
|
||||
--secondary-hover: #2a0070;
|
||||
--secondary-border-hover: #a168ff;
|
||||
--paper: #252526;
|
||||
--paper-border: #151516;
|
||||
|
||||
--paper-hover: #52366f;
|
||||
--paper-border-hover: #653497;
|
||||
|
||||
--info: #451376;
|
||||
--info-border: #210b36;
|
||||
|
||||
--dialog-border-color: black;
|
||||
--dialog-border-width: 2px;
|
||||
--dialog-radius: 6px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
@using WebAssembly.Components.Inputs
|
||||
@implements IDisposable;
|
||||
@inject IMyDialogService MyDialogService
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (MyDialogService.IsVisible)
|
||||
{
|
||||
<div class="confirmDialogBackground" onclick="@CloseDialog">
|
||||
<div class="confirmDialogContainer"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true">
|
||||
|
||||
<div class="confirmDialogHeader">
|
||||
@MyDialogService.GetDialogContents().Title
|
||||
</div>
|
||||
<div class="confirmDialogBody">
|
||||
@MyDialogService.GetDialogContents().Message
|
||||
</div>
|
||||
|
||||
<div class="confirmDialogFooter">
|
||||
<ButtonComponent MyButtonType="MyButtonType.Secondary"
|
||||
OnClick="MyDialogService.GetDialogContents().OnCancel">
|
||||
Cancel
|
||||
</ButtonComponent>
|
||||
<ButtonComponent MyButtonType="MyButtonType.Primary"
|
||||
OnClick="MyDialogService.GetDialogContents().OnConfirm">
|
||||
@MyDialogService.GetDialogContents().ConfirmButtonLabel
|
||||
</ButtonComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pageContents * {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.confirmDialogBackground {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.confirmDialogContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
|
||||
/**
|
||||
background-color: var(--background);
|
||||
border-width: var(--dialog-border-width);
|
||||
border-style: solid;
|
||||
border-color: var(--dialog-border-color);
|
||||
border-radius: var(--dialog-radius);
|
||||
*/
|
||||
padding: 8px;
|
||||
|
||||
|
||||
box-shadow: 1px 2px 2px black;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.confirmDialogHeader {
|
||||
font-size: 1.4em;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.confirmDialogBody {
|
||||
padding: 12px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.confirmDialogFooter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
MyDialogService.Subscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
|
||||
public void CloseDialog()
|
||||
{
|
||||
MyDialogService.Hide();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
@using WebAssembly.Components.Inputs
|
||||
@implements IDisposable;
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
@if (MyDialogService.IsVisible && MyDialogService.GetDialogContents().TechStack != null)
|
||||
{
|
||||
var techStack = MyDialogService.GetDialogContents().TechStack;
|
||||
|
||||
<div class="techStackDialogBackground" onclick="@CloseDialog">
|
||||
<div class="techStackDialogContainer"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true">
|
||||
|
||||
<div class="techStackDialogHeader">
|
||||
@techStack.Name
|
||||
</div>
|
||||
<div class="techStackDialogBody">
|
||||
<div class="description">
|
||||
@techStack.Description
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="extendedNotes">
|
||||
@((MarkupString)(techStack.ExtendedNotes?.Replace("\n", "<br />") ?? ""))
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="techStackDialogFooter">
|
||||
<ButtonComponent MyButtonType="MyButtonType.Primary"
|
||||
OnClick="CloseDialog">
|
||||
Close
|
||||
</ButtonComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pageContents * {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.techStackDialogBackground {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.techStackDialogContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
width: 800px;
|
||||
max-height: 80vh;
|
||||
background-color: var(--paper);
|
||||
border-width: var(--dialog-border-width);
|
||||
border-style: solid;
|
||||
border-color: var(--dialog-border-color);
|
||||
border-radius: var(--dialog-radius);
|
||||
padding: 16px;
|
||||
box-shadow: 1px 2px 2px black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.techStackDialogHeader {
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--paper-border);
|
||||
}
|
||||
|
||||
.techStackDialogBody {
|
||||
padding: 16px 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-style: italic;
|
||||
margin-bottom: 16px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.extendedNotes {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.techStackDialogFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--paper-border);
|
||||
}
|
||||
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(StateHasChanged);
|
||||
|
||||
Console.WriteLine("TechStackDialogComponent initialized");
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(StateHasChanged);
|
||||
}
|
||||
|
||||
public void CloseDialog()
|
||||
{
|
||||
Console.WriteLine( "Closing dialog");
|
||||
MyDialogService.Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
@inject IToastService ToastService
|
||||
|
||||
@using WebAssembly.Data
|
||||
@implements IDisposable
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
@if (Toast == null)
|
||||
{
|
||||
<div>Add toast object...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div onclick="@Dismiss" style="opacity: @Opacity()" class="toastContainer @Toast.SeverityType.ToLower()">
|
||||
<div class="toastTitle">
|
||||
@Toast.Title
|
||||
</div>
|
||||
<div>
|
||||
@Toast.Message
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<style>
|
||||
.toastContainer {
|
||||
border: 4px solid;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-items: stretch;
|
||||
width: 250px;
|
||||
cursor: pointer;
|
||||
}
|
||||
/**
|
||||
.@SeverityType.Warning.ToLower() {
|
||||
background-color: var(--severity-warning-color);
|
||||
border-color: var(--severity-warning-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Error.ToLower() {
|
||||
background-color: var(--severity-error-color);
|
||||
border-color: var(--severity-error-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Information.ToLower() {
|
||||
background-color: var(--severity-information-color);
|
||||
border-color: var(--severity-information-border-color);
|
||||
}
|
||||
|
||||
.@SeverityType.Success.ToLower() {
|
||||
background-color: var(--severity-success-color);
|
||||
border-color: var(--severity-success-border-color);
|
||||
}*/
|
||||
|
||||
.toastTitle {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public ToastModel? Toast { get; set; }
|
||||
|
||||
private readonly float removalTime = 1300;
|
||||
private readonly float fadeoutTime = 1200;
|
||||
|
||||
private float Opacity()
|
||||
{
|
||||
if (Toast!.Age < fadeoutTime)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1.0f - (Toast.Age - fadeoutTime) / (removalTime - fadeoutTime);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
ToastService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void Dismiss()
|
||||
{
|
||||
ToastService.RemoveToast(Toast!);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
ToastService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
if (Toast!.Age > removalTime)
|
||||
{
|
||||
ToastService.RemoveToast(Toast);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<button class="buttonContainer @MyButtonType.ToString().ToLower()" @onclick="ButtonClicked">@ChildContent</button>
|
||||
|
||||
<style>
|
||||
.buttonContainer {
|
||||
padding: 16px;
|
||||
border: 1px solid;
|
||||
border-radius: 8px;
|
||||
font-weight: 800;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.@(MyButtonType.Primary.ToString().ToLower()) {
|
||||
border-color: var(--primary);
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.@MyButtonType.Secondary.ToString().ToLower() {
|
||||
border-color: var(--secondary);
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.@MyButtonType.Primary.ToString().ToLower():hover {
|
||||
background-color: var(--primary-hover);
|
||||
border-color: var(--primary-border-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.@MyButtonType.Secondary.ToString().ToLower():hover {
|
||||
background-color: var(--secondary-hover);
|
||||
border-color: var(--secondary-border-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
|
||||
|
||||
[Parameter] public EventCallback<EventArgs> OnClick { get; set; }
|
||||
|
||||
[Parameter] public MyButtonType MyButtonType { get; set; }
|
||||
|
||||
private void ButtonClicked(EventArgs eventArgs)
|
||||
{
|
||||
OnClick.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WebAssembly.Components.Inputs;
|
||||
|
||||
public enum MyButtonType
|
||||
{
|
||||
Primary, // Positive Actions
|
||||
Secondary // Destruction Action
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@using MudBlazor
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudPopoverProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<div class="page">
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
@@ -0,0 +1,98 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
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:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public enum MagicMaterialCategory
|
||||
{
|
||||
Ore,
|
||||
SunlessOre,
|
||||
Liquid,
|
||||
Plant,
|
||||
VoidStone
|
||||
}
|
||||
|
||||
public class MagicMaterial
|
||||
{
|
||||
public MagicMaterialCategory Category { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string? GlobalBonus { get; set; }
|
||||
public string? InfusionEffects1 { get; set; }
|
||||
public string? InfusionEffects2 { get; set; }
|
||||
public string? InfusionEffects3 { get; set; }
|
||||
public int? IncreaseProduction { get; set; }
|
||||
public int? IncreaseGold { get; set; }
|
||||
public int? IncreaseMana { get; set; }
|
||||
public int? IncreaseDraft { get; set; }
|
||||
public int? IncreaseKnowledge { get; set; }
|
||||
public int? IncreaseFood { get; set; }
|
||||
public int? IncreaseStability { get; set; }
|
||||
public int? IncreaseImperium { get; set; }
|
||||
public int? IncreaseAllegianceFromWhisperingStones { get; set; }
|
||||
public int? IncreaseRelationWithFreeCitiesAndRulers { get; set; }
|
||||
public int? IncreaseCombatCastingPoints { get; set; }
|
||||
public int? IncreaseWorldCastingPoints { get; set; }
|
||||
public int? IncreaseHpRegen { get; set; }
|
||||
public int? IncreaseHitPoints { get; set; }
|
||||
public int? IncreaseExperiencePercent { get; set; }
|
||||
public int? IncreaseAllegianceFromUmbralDwellings { get; set; }
|
||||
public int? DecreaseDraftCostPercent { get; set; }
|
||||
public int? DecreaseRecruitmentCostPercent { get; set; }
|
||||
public int? DecreaseKnowledgeResearchCostPercent { get; set; }
|
||||
public int? DecreaseTurnsTakenToFoundAbsorbMigrateCities { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public static class MagicMaterialsData
|
||||
{
|
||||
public static readonly IReadOnlyList<MagicMaterial> RawData = new List<MagicMaterial>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Ore,
|
||||
Name = "Arcanum Ore",
|
||||
Description =
|
||||
"Desolate, Cave Underground and Desolate Underground only. Collection effect: Rings of Binding.",
|
||||
IncreaseProduction = 10,
|
||||
IncreaseMana = 10,
|
||||
GlobalBonus = "-25% Hurry Recruitment Cost",
|
||||
InfusionEffects1 = """
|
||||
Inflict Sundered Defense
|
||||
Power Cleave
|
||||
Support - Bolstered Defense
|
||||
Reinforced
|
||||
+2 Defense
|
||||
Bolstering Defense
|
||||
Juggernaut
|
||||
Demolisher
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Construct Slayer
|
||||
Dragon Slayer
|
||||
Inflict Immobilized
|
||||
Push Back
|
||||
Displace and Replace
|
||||
+3 Defense
|
||||
Siege Master
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Ore,
|
||||
Name = "Focus Crystals",
|
||||
Description =
|
||||
"Desolate, Cave Underground and Desolate Underground only. Collection effect: Rings of Binding.",
|
||||
IncreaseGold = 10,
|
||||
IncreaseKnowledge = 10,
|
||||
GlobalBonus = "+10% Unit Experience Gain",
|
||||
InfusionEffects1 = """
|
||||
Retaliator +50%
|
||||
+1 Range
|
||||
Inflict Marked
|
||||
Damage Reflection 30%
|
||||
+20% Accuracy
|
||||
Lightning Strike
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Retaliator +100%
|
||||
Elemental Slayer
|
||||
Inflict Stunned
|
||||
Hyper-Awareness
|
||||
+30% Accuracy
|
||||
Inversion
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Area Damage - Line
|
||||
Damage Reflection 40%
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Ore,
|
||||
Name = "Fireforge Stone",
|
||||
Description =
|
||||
"Desolate, Cave Underground and Desolate Underground only. Collection effect: Rings of Binding.",
|
||||
IncreaseProduction = 20,
|
||||
DecreaseDraftCostPercent = 20,
|
||||
GlobalBonus = "-20% Unit Draft Cost",
|
||||
InfusionEffects1 = """
|
||||
Fire Damage
|
||||
+20% Critical Damage
|
||||
Inflict Burning
|
||||
Support - Strengthened
|
||||
Lesser Fire Shield
|
||||
+2 Fire Resistance
|
||||
Berserker's Rage
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Arcfire Damage
|
||||
+40% Critical Damage
|
||||
Plant Slayer
|
||||
Greater Fire Shield
|
||||
+4 Fire Resistance
|
||||
Ignore 4 Status Resistance
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Area Damage - Blast
|
||||
Inflict Insanity
|
||||
Consume Chaos
|
||||
+6 Fire Resistance
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.SunlessOre,
|
||||
Name = "Blood Glass",
|
||||
Description = "Sunless Terrain only. Counts as Ore.",
|
||||
IncreaseDraft = 20,
|
||||
IncreaseHpRegen = 5,
|
||||
GlobalBonus = "+5 HP regeneration (on the world map)",
|
||||
InfusionEffects1 = """
|
||||
Greater Inflict Bleed
|
||||
Lifedrinker
|
||||
Blood Sigil
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Liquid,
|
||||
Name = "Archon Blood",
|
||||
Description = "Arctic, Highlands and Arctic Underground only. Collection effect: Cosmoflux Elixir.",
|
||||
IncreaseMana = 20,
|
||||
IncreaseCombatCastingPoints = 15,
|
||||
GlobalBonus = "+15 Combat Casting Points",
|
||||
InfusionEffects1 = """
|
||||
Frost Damage
|
||||
Infecting
|
||||
Assassinate
|
||||
Life Steal
|
||||
Lesser Frost Shield
|
||||
+10 Hit Points
|
||||
+2 Frost Resistance
|
||||
Vicious Killer
|
||||
Flanker
|
||||
Raise Undead
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Death Damage
|
||||
Inflict Diseased
|
||||
+15 Hit Points
|
||||
+4 Frost Resistance
|
||||
Bolstering Regeneration
|
||||
Undying
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Inflict Decaying
|
||||
Gravecall
|
||||
Greater Frost Shield
|
||||
+6 Frost Resistance
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Liquid,
|
||||
Name = "Astral Dew",
|
||||
Description = "Arctic, Highlands and Arctic Underground only. Collection effect: Cosmoflux Elixir.",
|
||||
IncreaseMana = 10,
|
||||
IncreaseKnowledge = 10,
|
||||
IncreaseWorldCastingPoints = 15,
|
||||
GlobalBonus = "+15 World Map Casting Points",
|
||||
InfusionEffects1 = """
|
||||
Lightning Damage
|
||||
Inflict Status Vulnerability
|
||||
Inflict Sundered Resistance
|
||||
Support - Bolstered Resistance
|
||||
Lesser Lightning Shield
|
||||
Warded
|
||||
+2 Lightning Resistance
|
||||
Slip Away
|
||||
Casting Points +20
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Magic Origin Slayer
|
||||
Inflict Frozen
|
||||
Greater Lightning Shield
|
||||
+4 Lightning Resistance
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Static Shield
|
||||
+6 Lightning Resistance
|
||||
Pass Through
|
||||
Astral Membrane
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Liquid,
|
||||
Name = "Tranquility Pool",
|
||||
Description = "Arctic, Highlands and Arctic Underground only. Collection effect: Cosmoflux Elixir.",
|
||||
IncreaseKnowledge = 20,
|
||||
DecreaseKnowledgeResearchCostPercent = 10,
|
||||
GlobalBonus = "-10% Knowledge research cost for spells",
|
||||
InfusionEffects1 = """
|
||||
Inflict Slowed
|
||||
Inflict Weakened
|
||||
Inflict Wet
|
||||
Support - Status Protection
|
||||
Lesser Spirit Shield
|
||||
+2 Resistance
|
||||
+2 Status Resistance
|
||||
Attunement: Star Blades
|
||||
Bolstering Resistance
|
||||
Slippery
|
||||
Hindering Blizzard
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Celestial Slayer
|
||||
Undead Slayer
|
||||
+3 Resistance
|
||||
+3 Status Resistance
|
||||
Attunement: Fortune
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Area Damage - Cascade
|
||||
Status Effect Immunity
|
||||
Resurrection
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Plant,
|
||||
Name = "Haste Berries",
|
||||
Description = "Desert, Temperate and Tropical only. Collection effect: Imperial Essence.",
|
||||
IncreaseDraft = 20,
|
||||
DecreaseTurnsTakenToFoundAbsorbMigrateCities = 2,
|
||||
GlobalBonus = "-2 turns to found, absorb or migrate cities",
|
||||
InfusionEffects1 = """
|
||||
Frenzy
|
||||
Inflict Distracted
|
||||
Swift
|
||||
Wind Barrier
|
||||
Conjure Animal
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Extra Retaliation
|
||||
Giant Slayer
|
||||
Inflict Blinded
|
||||
Whirlwind
|
||||
Defensive Masters
|
||||
Very Fast Movement
|
||||
Killing Momentum
|
||||
Animate Flora
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
Area Damage - Chain
|
||||
Polymorph
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Plant,
|
||||
Name = "Silvertongue Fruit",
|
||||
Description = "Desert, Temperate and Tropical only. Collection effect: Imperial Essence.",
|
||||
IncreaseFood = 10,
|
||||
IncreaseDraft = 10,
|
||||
IncreaseAllegianceFromWhisperingStones = 1,
|
||||
GlobalBonus = "+1 Allegiance from Whispering Stones",
|
||||
InfusionEffects1 = """
|
||||
Blight Damage
|
||||
Inflict Condemned
|
||||
Inflict Poisoned
|
||||
Support - Regeneration
|
||||
Lesser Blight Shield
|
||||
+2 Blight Resistance
|
||||
Inspiring Killer
|
||||
Universal Camouflage
|
||||
Army Trainer
|
||||
Summon Spider
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Hero Slayer
|
||||
Inflict Despairing
|
||||
Inflict Taunted
|
||||
Greater Blight Shield
|
||||
+4 Blight Resistance
|
||||
Army Maintenance
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
+6 Blight Resistance
|
||||
Domination
|
||||
Summon Spider Monarch
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.Plant,
|
||||
Name = "Rainbow Clover",
|
||||
Description = "Desert, Temperate and Tropical only. Collection effect: Imperial Essence.",
|
||||
IncreaseFood = 10,
|
||||
IncreaseStability = 10,
|
||||
IncreaseRelationWithFreeCitiesAndRulers = 100,
|
||||
GlobalBonus = "+100 Relations with Free Cities and Rulers",
|
||||
InfusionEffects1 = """
|
||||
Spirit Damage
|
||||
+2 Spirit Resistance
|
||||
Zeal
|
||||
Army Recuperation
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Radiant Damage
|
||||
+20% Critical Hit chance
|
||||
Fiend Slayer
|
||||
+4 Spirit Resistance
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
+30% Critical Hit chance
|
||||
+6 Spirit Resistance
|
||||
Mass Heal
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Category = MagicMaterialCategory.VoidStone,
|
||||
Name = "Void Stones",
|
||||
Description = "Umbral Abyss only. Collection effect: Void Ink.",
|
||||
IncreaseMana = 30,
|
||||
IncreaseAllegianceFromUmbralDwellings = 2,
|
||||
GlobalBonus = "+2 Allegiance per turn with discovered Umbral Dwellings",
|
||||
InfusionEffects1 = """
|
||||
Boon Stealing
|
||||
Cleansing Fire
|
||||
Army: Umbral Malady Immunity
|
||||
""",
|
||||
InfusionEffects2 = """
|
||||
Splitterling Infection
|
||||
Summon Umbral Demon
|
||||
""",
|
||||
InfusionEffects3 = """
|
||||
True Damage
|
||||
"""
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Province Improvement - a buildable enhancement that can be constructed in a province.
|
||||
/// Each Province Improvement decreases stability by -5.
|
||||
/// </summary>
|
||||
public class ProvinceImprovement
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the province improvement.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The base category/type (Conduit, Farm, Forester, Mine, Quarry, Research Post, Teleporter, Monument, etc.).
|
||||
/// </summary>
|
||||
public required string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A description of the effects this improvement provides (resource bonuses, adjacency bonuses, special mechanics,
|
||||
/// etc.).
|
||||
/// </summary>
|
||||
public required string Effects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any requirements to build this improvement (terrain, resource nodes, Town Hall tier, etc.).
|
||||
/// </summary>
|
||||
public required string Requirements { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The source/culture/tome that grants this improvement (General, Barbarian, Feudal, High, Mystic, etc.).
|
||||
/// </summary>
|
||||
public required string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The production cost to build this improvement.
|
||||
/// </summary>
|
||||
public int CostProduction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The gold cost to build this improvement.
|
||||
/// </summary>
|
||||
public int CostGold { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Static raw data provider for Province Improvements.
|
||||
/// </summary>
|
||||
public static class ProvinceImprovementsData
|
||||
{
|
||||
public static List<ProvinceImprovement> RawData =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "Conduit",
|
||||
Category = "Conduit",
|
||||
Effects = "+5 Mana",
|
||||
Requirements = "Mana node, pearl reef, or magic material",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Farm",
|
||||
Category = "Farm",
|
||||
Effects = "+5 Food",
|
||||
Requirements = "Grassland or coast terrain, fungus fields (underground)",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Forester",
|
||||
Category = "Forester",
|
||||
Effects = "+2 Food\n+3 Production",
|
||||
Requirements = "Forest or mangrove forest terrain, mushroom forest (underground)",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Hut",
|
||||
Category = "Hut",
|
||||
Effects = "+2 Food",
|
||||
Requirements = "Ashlands, sand or snow terrain",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Mine",
|
||||
Category = "Mine",
|
||||
Effects = "+5 Gold",
|
||||
Requirements = "Gold vein, iron deposit, or pearl reef resource node",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Quarry",
|
||||
Category = "Quarry",
|
||||
Effects = "+5 Production",
|
||||
Requirements = "Cliff, rocky, or sunken ruins terrain; or iron deposit resource node",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Research Post",
|
||||
Category = "Research Post",
|
||||
Effects = "+5 Knowledge",
|
||||
Requirements = "Mana node, magic material or sunken ruins terrain",
|
||||
Source = "Base",
|
||||
CostProduction = 0,
|
||||
CostGold = 0
|
||||
},
|
||||
|
||||
// General Province Improvements
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Spell Jammer",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
Enemies cannot target World Map Spells in this Domain.
|
||||
Enemy Spells cost +100% Combat Casting Points in Combat in Domain.
|
||||
-10 Mana.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall III",
|
||||
Source = "General",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Teleporter",
|
||||
Category = "Teleporter",
|
||||
Effects = """
|
||||
A Province Improvement which enables an Army to teleport from one teleporter to another.
|
||||
-10 Mana.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province",
|
||||
Source = "General",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Barbarian
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Forest of Stakes",
|
||||
Category = "Forester",
|
||||
Effects = """
|
||||
+7 Food income.
|
||||
+7 Production income.
|
||||
+7 Draft per adjacent Forester.
|
||||
Enemy Units in this Domain get Demoralized.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Communal Tent",
|
||||
Source = "Barbarian",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Dark Cult of Death
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Masoleum",
|
||||
Category = "Research Post",
|
||||
Effects = """
|
||||
+10 Knowledge income.
|
||||
+3 Knowledge Production per adjacent Conduit.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Dread Spire",
|
||||
Source = "Dark - Cult of Death",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Dark Cult of Tyranny
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Dark Forge",
|
||||
Category = "Mine",
|
||||
Effects = """
|
||||
+10 Gold income.
|
||||
+5 Production per adjacent Quarry, Mine, or Forester.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Dread Spire",
|
||||
Source = "Dark - Cult of Tyranny",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Feudal
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Farmstead",
|
||||
Category = "Farm",
|
||||
Effects = """
|
||||
+15 Food income.
|
||||
+5 Food per adjacent Farm.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Castle",
|
||||
Source = "Feudal",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - High
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Sunshrine",
|
||||
Category = "Research Post",
|
||||
Effects = """
|
||||
+10 Knowledge income.
|
||||
+3 Knowledge per adjacent Research Post.
|
||||
Friendly Units in this Domain are Encouraged.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Atrium of Light",
|
||||
Source = "High",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Industrious
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Builder's Quarters",
|
||||
Category = "Quarry",
|
||||
Effects = """
|
||||
+15 Production income.
|
||||
+5 Production per adjacent Quarry.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Bulwark",
|
||||
Source = "Industrious",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Mystic School of Attunement
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Mystic Abbey",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
+10 Mana income.
|
||||
+3 Knowledge per adjacent Conduit or Research Post.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Mage's Plaza",
|
||||
Source = "Mystic - School of Attunement",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Mystic School of Summoning
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Astral Manalith",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
+10 Mana income.
|
||||
+5 Mana per adjacent Conduit or Research Post.
|
||||
""",
|
||||
Requirements =
|
||||
"Must be built on an acquired Province.\nRequires City Tier 2.\nRequires Town Hall II: Mage's Plaza",
|
||||
Source = "Mystic - School of Summoning",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Nomad Conquerors
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Warcamp",
|
||||
Category = "Forester",
|
||||
Effects = """
|
||||
+15 Draft.
|
||||
Pillaging adjacent provinces takes -1 Turn.
|
||||
Friendly units in this and adjacent provinces gain +15 Hit Point regeneration per World Map Turn.
|
||||
""",
|
||||
Requirements = "Must be built on an annexed Province.\nRequires Town Hall II: Council Deck",
|
||||
Source = "Nomad - Conquerors",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Cultural Province Improvements - Nomad Scavengers
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Scavenging Camp",
|
||||
Category = "Mine",
|
||||
Effects = """
|
||||
+10 Gold.
|
||||
+10 Gold for this and each adjacent province containing a Resource Node.
|
||||
""",
|
||||
Requirements = "Must be built on an annexed Province.\nRequires Town Hall II: Council Deck",
|
||||
Source = "Nomad - Scavengers",
|
||||
CostProduction = 130,
|
||||
CostGold = 60
|
||||
},
|
||||
|
||||
// Primal Megalith improvements
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Ash Megalith",
|
||||
Category = "Conduit",
|
||||
Effects = "+6 Gold per adjacent Ashland Province",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Clan Lodge",
|
||||
Source = "Primal - Ash Sabertooth",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Dune Megalith",
|
||||
Category = "Conduit",
|
||||
Effects = "+4 Gold per adjacent Sand Province",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Clan Lodge",
|
||||
Source = "Primal - Dune Serpent",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "Glacial Megalith",
|
||||
Category = "Conduit",
|
||||
Effects = "+6 Production per adjacent Snow Province",
|
||||
Requirements = "Must be built on an acquired province.\nRequires Town Hall II: Clan Lodge",
|
||||
Source = "Primal - Glacial Mammoth",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
// Oathsworn Schools
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "School of Contemplation",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
+7 Mana.
|
||||
+7 Knowledge.
|
||||
+3 Knowledge per adjacent Conduit or Research Post.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired Province.\nRequires Town Hall II: Oath Square",
|
||||
Source = "Oathsworn - Oath of Harmony",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "School of Discipline",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
+7 Mana.
|
||||
+7 Draft.
|
||||
+5 Draft per adjacent Quarry.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired Province.\nRequires Town Hall II: Oath Square",
|
||||
Source = "Oathsworn - Oath of Righteousness",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Name = "School of Mastery",
|
||||
Category = "Conduit",
|
||||
Effects = """
|
||||
+7 Mana.
|
||||
+7 Draft.
|
||||
Units produced in this city start with +1 Starting Rank.
|
||||
""",
|
||||
Requirements = "Must be built on an acquired Province.\nRequires Town Hall II: Oath Square",
|
||||
Source = "Oathsworn - Oath of Strife",
|
||||
CostProduction = 250,
|
||||
CostGold = 100
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class ResourceNode
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public int? IncreaseFood { get; set; }
|
||||
public int? IncreaseProduction { get; set; }
|
||||
public int? IncreaseGold { get; set; }
|
||||
public int? IncreaseMana { get; set; }
|
||||
public int? IncreaseKnowledge { get; set; }
|
||||
public bool ForceEnableFarm { get; set; }
|
||||
public bool ForceEnableMine { get; set; }
|
||||
public bool ForceEnableQuarry { get; set; }
|
||||
public bool ForceEnableConduit { get; set; }
|
||||
public bool ForceEnableResearchPost { get; set; }
|
||||
public bool ForceEnableForester { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public static class ResourceNodesData
|
||||
{
|
||||
public static readonly IReadOnlyList<ResourceNode> RawData = new List<ResourceNode>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "Pastures",
|
||||
Description = "Roaming herds on lush fields.",
|
||||
IncreaseFood = 10,
|
||||
ForceEnableFarm = true
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Oasis",
|
||||
Description = "A lush oasis full of nutritious food.",
|
||||
IncreaseFood = 10
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Iron Deposit",
|
||||
Description = "A rich vein full of ore.",
|
||||
IncreaseProduction = 10,
|
||||
ForceEnableMine = true,
|
||||
ForceEnableQuarry = true
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Gold Vein",
|
||||
Description = "A large vein of valuable gold.",
|
||||
IncreaseGold = 10,
|
||||
ForceEnableMine = true
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Mana Node",
|
||||
Description = "Magical currents converge at this location.",
|
||||
IncreaseMana = 10,
|
||||
ForceEnableConduit = true,
|
||||
ForceEnableResearchPost = true
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Fishing Ground",
|
||||
Description = "A plentiful source of fish.",
|
||||
IncreaseFood = 15
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Pearl Reef",
|
||||
Description = "A bloom of valuable pearls.",
|
||||
IncreaseGold = 7,
|
||||
IncreaseMana = 7,
|
||||
ForceEnableMine = true,
|
||||
ForceEnableConduit = true
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Chitinous Growths",
|
||||
Description = "These grotesque growths deposit valuable liquid and ore at unnatural speed.",
|
||||
IncreaseGold = 30
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Monoliths",
|
||||
Description = "There is writing in an unknown language on these obsidian giants.",
|
||||
IncreaseKnowledge = 30
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Blossom Orchard",
|
||||
Description = "Eternally blooming trees offering bounty of fruit and wood.",
|
||||
IncreaseFood = 5,
|
||||
IncreaseProduction = 5,
|
||||
ForceEnableForester = true
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class SearchPointModel
|
||||
{
|
||||
public string Title { get; set; } = "";
|
||||
public string Summary { get; set; } = "";
|
||||
|
||||
public string Tags { get; set; } = "";
|
||||
public string PointType { get; set; } = "";
|
||||
public string Href { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class SectionLink
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
public class Section
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public List<SectionLink> Links { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public static class SectionsData
|
||||
{
|
||||
public static List<Section> GetAllSections()
|
||||
{
|
||||
return
|
||||
[
|
||||
new Section
|
||||
{
|
||||
Name = "About",
|
||||
Description = "Meta information on this website",
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Tech Stack",
|
||||
Url = "/tech-stack",
|
||||
Description = "Information about the technology stack that will be used in this project."
|
||||
}
|
||||
]
|
||||
},
|
||||
new Section
|
||||
{
|
||||
Name = "Calculators",
|
||||
Description = "Useful calculator tools for various computations",
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Building Plan Calculator",
|
||||
Url = "/building-calculator",
|
||||
Description = "Simulate build order timing and gold income over turns."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
new Section
|
||||
{
|
||||
Name = "References",
|
||||
Description = "Reference materials and documentation",
|
||||
Links =
|
||||
[
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Magic Materials Reference",
|
||||
Url = "/references/magic-materials",
|
||||
Description = "View the magic material dataset and bonuses in a reference table."
|
||||
},
|
||||
|
||||
new SectionLink
|
||||
{
|
||||
Title = "Province Improvements Reference",
|
||||
Url = "/references/province-improvements",
|
||||
Description =
|
||||
"View the province improvements dataset including costs, effects, and requirements."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
new Section
|
||||
{
|
||||
Name = "Learning",
|
||||
Description = "Educational resources and learning materials",
|
||||
Links = []
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class SeverityType
|
||||
{
|
||||
public static string Warning = "Warning";
|
||||
public static string Information = "Information";
|
||||
public static string Error = "Error";
|
||||
public static string Success = "Success";
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class TechStack
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string? ExtendedNotes { get; set; }
|
||||
|
||||
public bool InUse { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public static class TechStackData
|
||||
{
|
||||
public static List<TechStack> RawData =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "Blazor WebAssembly",
|
||||
Description = "Framework for web applications for easy distribution and no hosting costs with third parties",
|
||||
InUse = true,
|
||||
ExtendedNotes = """
|
||||
Simple and easy to distribute. I like C#, and it's C# web development, what's not to like.
|
||||
Obviously con is if I want the user to save state between usages, I am relying on something unreliable like local storage which can be cleared by the user or obviously not transferred between different browsers.
|
||||
So needs to be used in reference and informational only content. I suppose I could rely on the user to handle copying and pasting the data, but a tad cumbersome and unrealistic to expect that.
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Blazor Server",
|
||||
Description = "Framework for web applications that allows for database interactions",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
Easy to distribute. I'll need to have to deal with authentication and all those security concerns.
|
||||
Needs a database to store data. Local hosting Postgresql is the plan.
|
||||
Unideal support implications. Need to make deleting all user data very easy for legal compliance.
|
||||
Less reliability. Server can go down, and I can have a long outage. Fallback to Blazor WebAssembly version hosted by a third party.
|
||||
"""
|
||||
},
|
||||
new() {
|
||||
Name = "MAUI",
|
||||
Description = "Framework for mobile and desktop applications",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
So I get around the whole unreliableness of web storage by saving files locally.
|
||||
I can easily distribute via an exe file or apk. Obviously con of that is the user needs to install a exe file or apk, so there is going be a clear 'this could be a virus' type of warning popup to discourage the user.
|
||||
Potentially can distribute via Google Play or Windows Store and be subject to the whims and veto power of a third party.
|
||||
"""
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "PostgreSQL",
|
||||
Description = "Relational database management system",
|
||||
InUse = false,
|
||||
ExtendedNotes = """
|
||||
I am picking PostgreSQL because it appears free and simple. I have pgAdmin installed and it running.
|
||||
Need to just actually implement using it in the far future.
|
||||
"""
|
||||
},
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace WebAssembly.Data;
|
||||
|
||||
public class ToastModel
|
||||
{
|
||||
public string Title { get; set; } = "addTitle";
|
||||
public string Message { get; set; } = "addMessage";
|
||||
public string SeverityType { get; set; } = "addType";
|
||||
public float Age { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using WebAssembly.Data;
|
||||
using WebAssembly.Services;
|
||||
|
||||
namespace WebAssembly;
|
||||
|
||||
public interface IToastService
|
||||
{
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
void AddToast(ToastModel toast);
|
||||
void RemoveToast(ToastModel toast);
|
||||
bool HasToasts();
|
||||
List<ToastModel> GetToasts();
|
||||
void AgeToasts();
|
||||
void ClearAllToasts();
|
||||
}
|
||||
|
||||
|
||||
public interface ISearchService
|
||||
{
|
||||
public List<SearchPointModel> SearchPoints { get; set; }
|
||||
|
||||
public Dictionary<string, List<SearchPointModel>> Searches { get; set; }
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
|
||||
public void Search(string entityId);
|
||||
|
||||
public Task Load();
|
||||
|
||||
public bool IsLoaded();
|
||||
void Show();
|
||||
void Hide();
|
||||
}
|
||||
|
||||
public interface IMyDialogService
|
||||
{
|
||||
public bool IsVisible { get; set; }
|
||||
public void Subscribe(Action action);
|
||||
public void Unsubscribe(Action action);
|
||||
public void Show(DialogContents dialogContents);
|
||||
public DialogContents GetDialogContents();
|
||||
public void Hide();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@using MudBlazor
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudPopoverProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
|
||||
<div class="page">
|
||||
<main>
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
@@ -0,0 +1,98 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
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:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
@page "/building-calculator"
|
||||
@using System.Text.Json
|
||||
@using WebAssembly.Throwaway
|
||||
|
||||
<div class="calculator-page">
|
||||
<h1>Building Plan Calculator</h1>
|
||||
<p>Simulates resource income each turn and tracks build completion times for ordered buildings.</p>
|
||||
|
||||
<section>
|
||||
<h2>Build Order</h2>
|
||||
<table class="calculator-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Turn Requested</th>
|
||||
<th>Building</th>
|
||||
<th>Finish Turn</th>
|
||||
<th>Industry Remaining</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var entry in Result.BuildOrder)
|
||||
{
|
||||
<tr>
|
||||
<td>@entry.RequestedTurn</td>
|
||||
<td>@entry.Name</td>
|
||||
<td>@(entry.BuiltFinishTurn == 0 ? "Starting" : entry.BuiltFinishTurn.ToString())</td>
|
||||
<td>@entry.IndustryCostRemaining</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Gold Over Time</h2>
|
||||
<table class="calculator-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Turn</th>
|
||||
<th>Stored Gold</th>
|
||||
<th>Income</th>
|
||||
<th>Upkeep</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var snapshot in Result.ResourceHistory)
|
||||
{
|
||||
<tr>
|
||||
<td>@snapshot.Turn</td>
|
||||
<td>@snapshot.Stored.Gold</td>
|
||||
<td>@snapshot.TotalIncome.Gold</td>
|
||||
<td>@snapshot.TotalUpkeep.Gold</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Result JSON</h2>
|
||||
<pre class="json-output">@Json</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private BuildPlanResult Result = new();
|
||||
private string Json = string.Empty;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Result = BuildingPlanCalculator.CreateSampleBuildPlan();
|
||||
Json = JsonSerializer.Serialize(Result, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<style>
|
||||
.calculator-page {
|
||||
padding: 2rem;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.calculator-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
|
||||
.calculator-table th,
|
||||
.calculator-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #d3d3d3;
|
||||
text-align: left;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.calculator-table th {
|
||||
background-color: #f3f6fb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.json-output {
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
background: #1f2937;
|
||||
color: #f8fafc;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
font-family: Consolas, "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,19 @@
|
||||
@page "/counter"
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
@page "/"
|
||||
@using WebAssembly.Data
|
||||
|
||||
<div class="home-container">
|
||||
<h1>Welcome to AOW4 Game Reference</h1>
|
||||
<p class="subtitle">Basic game reference project developed with AI assisted coding and agents.</p>
|
||||
|
||||
<div class="sections-grid">
|
||||
@foreach (var section in sections)
|
||||
{
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h2>@section.Name</h2>
|
||||
@if (!string.IsNullOrEmpty(section.Description))
|
||||
{
|
||||
<p class="section-description">@section.Description</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="section-links">
|
||||
@if (section.Links.Any())
|
||||
{
|
||||
<ul>
|
||||
@foreach (var link in section.Links)
|
||||
{
|
||||
<li>
|
||||
<a href="@link.Url">
|
||||
<span class="link-title">@link.Title</span>
|
||||
@if (!string.IsNullOrEmpty(link.Description))
|
||||
{
|
||||
<span class="link-description">@link.Description</span>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="no-links">Coming soon...</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private List<Section> sections = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
sections = SectionsData.GetAllSections();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<style>
|
||||
.home-container {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sections-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-card:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.8rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.section-links {
|
||||
padding: 1.5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.section-links ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-links li {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-links li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-links a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: #f9f9f9;
|
||||
border-left: 4px solid #4a90e2;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.section-links a:hover {
|
||||
background-color: #f0f0f0;
|
||||
border-left-color: #2563eb;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.link-description {
|
||||
font-size: 0.85rem;
|
||||
color: #7f8c8d;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.no-links {
|
||||
text-align: center;
|
||||
color: #bdc3c7;
|
||||
font-style: italic;
|
||||
padding: 2rem 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@media (max-width: 768px) {
|
||||
.sections-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
@page "/references/magic-materials"
|
||||
@using WebAssembly.Data
|
||||
|
||||
<div class="page-container">
|
||||
<h1>Magic Materials Reference</h1>
|
||||
<p class="subtitle">A reference view of the `MagicMaterial` data loaded from `MagicMaterialsData.RawData`.</p>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Annex Resources</th>
|
||||
<th>Global Bonus</th>
|
||||
<th>Infusion 1</th>
|
||||
<th>Infusion 2</th>
|
||||
<th>Infusion 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var material in MagicMaterialsData.RawData)
|
||||
{
|
||||
<tr>
|
||||
<td>@material.Name</td>
|
||||
<td>@material.Category</td>
|
||||
<td>@FormatAnnexResources(material)</td>
|
||||
<td>@material.GlobalBonus</td>
|
||||
<td>
|
||||
<div class="preformatted">@material.InfusionEffects1</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="preformatted">@material.InfusionEffects2</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="preformatted">@material.InfusionEffects3</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
private static string FormatAnnexResources(MagicMaterial material)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
void Add(string label, int? value)
|
||||
{
|
||||
if (value.HasValue)
|
||||
{
|
||||
parts.Add(value.Value < 0 ? $"{value.Value}% {label}" : $"+{value.Value} {label}");
|
||||
}
|
||||
}
|
||||
|
||||
Add("Production", material.IncreaseProduction);
|
||||
Add("Gold", material.IncreaseGold);
|
||||
Add("Mana", material.IncreaseMana);
|
||||
Add("Draft", material.IncreaseDraft);
|
||||
Add("Knowledge", material.IncreaseKnowledge);
|
||||
Add("Food", material.IncreaseFood);
|
||||
Add("Stability", material.IncreaseStability);
|
||||
Add("Imperium", material.IncreaseImperium);
|
||||
Add("Allegiance from Whispering Stones", material.IncreaseAllegianceFromWhisperingStones);
|
||||
Add("Relations with Free Cities and Rulers", material.IncreaseRelationWithFreeCitiesAndRulers);
|
||||
Add("Combat Casting Points", material.IncreaseCombatCastingPoints);
|
||||
Add("World Map Casting Points", material.IncreaseWorldCastingPoints);
|
||||
Add("HP Regeneration", material.IncreaseHpRegen);
|
||||
Add("Hit Points", material.IncreaseHitPoints);
|
||||
Add("Experience Percent", material.IncreaseExperiencePercent);
|
||||
Add("Allegiance from Umbral Dwellings", material.IncreaseAllegianceFromUmbralDwellings);
|
||||
Add("Draft Cost Reduction", material.DecreaseDraftCostPercent);
|
||||
Add("Recruitment Cost Reduction", material.DecreaseRecruitmentCostPercent);
|
||||
Add("Knowledge Research Cost Reduction", material.DecreaseKnowledgeResearchCostPercent);
|
||||
Add("Turn Reduction to Found/Absorb/Migrate", material.DecreaseTurnsTakenToFoundAbsorbMigrateCities);
|
||||
|
||||
return parts.Count > 0 ? string.Join("; ", parts) : "—";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.preformatted {
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--bs-font-sans-serif);
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -0,0 +1,75 @@
|
||||
@page "/references/province-improvements"
|
||||
@using WebAssembly.Data
|
||||
|
||||
<div class="page-container">
|
||||
<h1>Province Improvements Reference</h1>
|
||||
<p class="subtitle">A reference view of the `ProvinceImprovement` data loaded from
|
||||
`ProvinceImprovementsData.RawData`.</p>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Source</th>
|
||||
<th>Cost (Prod/Gold)</th>
|
||||
<th>Effects</th>
|
||||
<th>Requirements</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var improvement in ProvinceImprovementsData.RawData)
|
||||
{
|
||||
<tr>
|
||||
<td>@improvement.Name</td>
|
||||
<td>@improvement.Category</td>
|
||||
<td>@improvement.Source</td>
|
||||
<td>@improvement.CostProduction / @improvement.CostGold</td>
|
||||
<td>
|
||||
<div class="preformatted">@improvement.Effects</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="preformatted">@improvement.Requirements</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
}
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
padding: 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.preformatted {
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--bs-font-sans-serif);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
@page "/tech-stack"
|
||||
@using MudBlazor
|
||||
@using WebAssembly.Services
|
||||
@inject IMyDialogService DialogService
|
||||
|
||||
<div class="techStackPage">
|
||||
<h1>Tech Stack</h1>
|
||||
|
||||
<div class="techStackGrid">
|
||||
@foreach (var tech in Data.TechStackData.RawData)
|
||||
{
|
||||
<div class="techStackItem @(tech.InUse ? "" : "notInUse")" @onclick="() => OpenDialog(tech)">
|
||||
<div class="techStackName">@tech.Name</div>
|
||||
<div class="techStackDescription">@tech.Description</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.techStackPage {
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.techStackGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.techStackItem {
|
||||
background-color: var(--paper);
|
||||
border: 1px solid var(--paper-border);
|
||||
border-radius: var(--dialog-radius);
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.techStackItem:hover {
|
||||
background-color: var(--paper-hover);
|
||||
border-color: var(--paper-border-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.techStackItem.notInUse {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.techStackName {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.techStackDescription {
|
||||
font-size: 1em;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private void OpenDialog(Data.TechStack tech)
|
||||
{
|
||||
DialogService.Show(new DialogContents
|
||||
{
|
||||
Title = tech.Name,
|
||||
TechStack = tech
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "/weather"
|
||||
@inject HttpClient Http
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates fetching data from the server.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p>
|
||||
<em>Loading...</em>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th aria-label="Temperature in Celsius">Temp. (C)</th>
|
||||
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[]? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
|
||||
}
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public string? Summary { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@using WebAssembly.Components.Dialog
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
<ConfirmationDialogComponent></ConfirmationDialogComponent>
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@using WebAssembly.Components.Dialog
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IMyDialogService MyDialogService
|
||||
|
||||
<TechStackDialogComponent></TechStackDialogComponent>
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
MyDialogService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
MyDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@using System.Timers
|
||||
@using WebAssembly.Components.Feedback
|
||||
@using WebAssembly.Data
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IToastService ToastService
|
||||
|
||||
@if (ToastService.HasToasts())
|
||||
{
|
||||
<div class="toastsContainer">
|
||||
@foreach (var toast in Toasts)
|
||||
{
|
||||
<ToastComponent Toast="toast"/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<style>
|
||||
.toastsContainer {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
right: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@code {
|
||||
private List<ToastModel> Toasts => ToastService.GetToasts();
|
||||
|
||||
private Timer _ageTimer = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
ToastService.Subscribe(OnUpdate);
|
||||
|
||||
_ageTimer = new Timer(10);
|
||||
_ageTimer.Elapsed += OnAge!;
|
||||
_ageTimer.Enabled = true;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
ToastService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
|
||||
void OnAge(object? sender, ElapsedEventArgs elapsedEventArgs)
|
||||
{
|
||||
ToastService.AgeToasts();
|
||||
_ageTimer.Enabled = true;
|
||||
}
|
||||
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using MudBlazor.Services;
|
||||
using WebAssembly;
|
||||
using WebAssembly.Services;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
|
||||
builder.Services.AddScoped<IToastService, ToastService>();
|
||||
builder.Services.AddScoped<IMyDialogService, MyDialogService>();
|
||||
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5221",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7097;http://localhost:5221",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WebAssembly.Data;
|
||||
|
||||
namespace WebAssembly.Services;
|
||||
|
||||
public class DialogContents
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string ConfirmButtonLabel { get; set; }
|
||||
public EventCallback<EventArgs> OnConfirm { get; set; }
|
||||
public EventCallback<EventArgs> OnCancel { get; set; }
|
||||
public TechStack? TechStack { get; set; }
|
||||
}
|
||||
|
||||
public class MyDialogService : IMyDialogService
|
||||
{
|
||||
private DialogContents _dialogContents = new();
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Show(DialogContents dialogContents)
|
||||
{
|
||||
_dialogContents = dialogContents;
|
||||
IsVisible = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public DialogContents GetDialogContents()
|
||||
{
|
||||
return _dialogContents;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
IsVisible = false;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using WebAssembly.Data;
|
||||
|
||||
namespace WebAssembly.Services;
|
||||
|
||||
public class SearchService : ISearchService
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
public List<SearchPointModel> SearchPoints { get; set; } = new();
|
||||
|
||||
public Dictionary<string, List<SearchPointModel>> Searches { get; set; } = new();
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Search(string entityId)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task Load()
|
||||
{
|
||||
Searches.Add("MagicMaterial", []);
|
||||
|
||||
foreach (var entity in MagicMaterialsData.RawData)
|
||||
{
|
||||
var title = entity.Name;
|
||||
var description = entity.Description ?? "";
|
||||
|
||||
var summary =
|
||||
|
||||
description.Length > 35
|
||||
? description[..30].Trim() + "..."
|
||||
: description.Length > 0
|
||||
? description
|
||||
: "";
|
||||
|
||||
SearchPoints.Add(new SearchPointModel
|
||||
{
|
||||
Title = title,
|
||||
Tags = "Magic Material",
|
||||
PointType = "Entity",
|
||||
Summary = summary,
|
||||
Href = ""// Add a link to the entity page
|
||||
});
|
||||
|
||||
Searches["MagicMaterial"].Add(SearchPoints.Last());
|
||||
}
|
||||
|
||||
_isLoaded = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public bool IsLoaded()
|
||||
{
|
||||
return _isLoaded;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
IsVisible = true;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
IsVisible = false;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using WebAssembly.Data;
|
||||
|
||||
namespace WebAssembly.Services;
|
||||
|
||||
public class ToastService : IToastService
|
||||
{
|
||||
private readonly List<ToastModel> _toasts = [];
|
||||
|
||||
public void Subscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void Unsubscribe(Action action)
|
||||
{
|
||||
OnChange += action;
|
||||
}
|
||||
|
||||
public void AddToast(ToastModel toast)
|
||||
{
|
||||
_toasts.Insert(0, toast);
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void RemoveToast(ToastModel toast)
|
||||
{
|
||||
_toasts.Remove(toast);
|
||||
}
|
||||
|
||||
public bool HasToasts()
|
||||
{
|
||||
return _toasts.Count > 0;
|
||||
}
|
||||
|
||||
public List<ToastModel> GetToasts()
|
||||
{
|
||||
return _toasts;
|
||||
}
|
||||
|
||||
public void AgeToasts()
|
||||
{
|
||||
foreach (var toast in _toasts) toast.Age++;
|
||||
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
public void ClearAllToasts()
|
||||
{
|
||||
_toasts.Clear();
|
||||
NotifyDataChanged();
|
||||
}
|
||||
|
||||
private event Action OnChange = null!;
|
||||
|
||||
private void NotifyDataChanged()
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WebAssembly.Throwaway;
|
||||
|
||||
public sealed class ResourceAmounts
|
||||
{
|
||||
public int Draft { get; set; }
|
||||
public int Food { get; set; }
|
||||
public int Knowledge { get; set; }
|
||||
public int Industry { get; set; }
|
||||
public int Magic { get; set; }
|
||||
public int Gold { get; set; }
|
||||
public int Imperial { get; set; }
|
||||
public int Stability { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsZero => Draft == 0 && Food == 0 && Knowledge == 0 && Industry == 0 && Magic == 0 && Gold == 0 &&
|
||||
Imperial == 0 && Stability == 0;
|
||||
|
||||
public void Add(ResourceAmounts other)
|
||||
{
|
||||
Draft += other.Draft;
|
||||
Food += other.Food;
|
||||
Knowledge += other.Knowledge;
|
||||
Industry += other.Industry;
|
||||
Magic += other.Magic;
|
||||
Gold += other.Gold;
|
||||
Imperial += other.Imperial;
|
||||
Stability += other.Stability;
|
||||
}
|
||||
|
||||
public void Subtract(ResourceAmounts other)
|
||||
{
|
||||
Draft -= other.Draft;
|
||||
Food -= other.Food;
|
||||
Knowledge -= other.Knowledge;
|
||||
Industry -= other.Industry;
|
||||
Magic -= other.Magic;
|
||||
Gold -= other.Gold;
|
||||
Imperial -= other.Imperial;
|
||||
Stability -= other.Stability;
|
||||
}
|
||||
|
||||
public static ResourceAmounts operator +(ResourceAmounts a, ResourceAmounts b)
|
||||
{
|
||||
return new ResourceAmounts
|
||||
{
|
||||
Draft = a.Draft + b.Draft,
|
||||
Food = a.Food + b.Food,
|
||||
Knowledge = a.Knowledge + b.Knowledge,
|
||||
Industry = a.Industry + b.Industry,
|
||||
Magic = a.Magic + b.Magic,
|
||||
Gold = a.Gold + b.Gold,
|
||||
Imperial = a.Imperial + b.Imperial,
|
||||
Stability = a.Stability + b.Stability
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceAmounts operator -(ResourceAmounts a, ResourceAmounts b)
|
||||
{
|
||||
return new ResourceAmounts
|
||||
{
|
||||
Draft = a.Draft - b.Draft,
|
||||
Food = a.Food - b.Food,
|
||||
Knowledge = a.Knowledge - b.Knowledge,
|
||||
Industry = a.Industry - b.Industry,
|
||||
Magic = a.Magic - b.Magic,
|
||||
Gold = a.Gold - b.Gold,
|
||||
Imperial = a.Imperial - b.Imperial,
|
||||
Stability = a.Stability - b.Stability
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BuildingDefinition
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string? Image { get; set; }
|
||||
public string SourceId { get; set; } = "General";
|
||||
public List<string> RequirementIds { get; set; } = new();
|
||||
public ResourceAmounts Income { get; set; } = new();
|
||||
public ResourceAmounts Upkeep { get; set; } = new();
|
||||
public ResourceAmounts Cost { get; set; } = new();
|
||||
public int BoostPopulation { get; set; }
|
||||
public int BoostForester { get; set; }
|
||||
public int BoostFarm { get; set; }
|
||||
public int BoostQuarry { get; set; }
|
||||
public int BoostGoldMine { get; set; }
|
||||
public int BoostConduit { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuildOrderEntry
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string ItemId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int RequestedTurn { get; set; }
|
||||
public int BuildStartTurn { get; set; }
|
||||
public int BuiltFinishTurn { get; set; }
|
||||
public int IndustryCostRemaining { get; set; }
|
||||
public BuildingDefinition Definition { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class ResourceSnapshot
|
||||
{
|
||||
public int Turn { get; set; }
|
||||
public ResourceAmounts Stored { get; set; } = new();
|
||||
public ResourceAmounts TotalIncome { get; set; } = new();
|
||||
public ResourceAmounts TotalUpkeep { get; set; } = new();
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BuildPlanResult
|
||||
{
|
||||
public List<BuildOrderEntry> BuildOrder { get; set; } = new();
|
||||
public List<ResourceSnapshot> ResourceHistory { get; set; } = new();
|
||||
public ResourceAmounts StartingPool { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class BuildOrderRequest
|
||||
{
|
||||
public string ItemId { get; init; } = string.Empty;
|
||||
public BuildingDefinition Definition { get; init; } = new();
|
||||
public int RequestedTurn { get; init; }
|
||||
}
|
||||
|
||||
public static class BuildingPlanCalculator
|
||||
{
|
||||
public static BuildPlanResult CalculateBuildPlan(
|
||||
int totalTurns,
|
||||
IEnumerable<BuildOrderRequest> buildRequests,
|
||||
IEnumerable<BuildingDefinition>? startingBuildings = null,
|
||||
ResourceAmounts? startingResources = null)
|
||||
{
|
||||
var activeBuildings = new List<BuildOrderEntry>();
|
||||
var result = new BuildPlanResult
|
||||
{
|
||||
StartingPool = startingResources ?? new ResourceAmounts()
|
||||
};
|
||||
|
||||
var stored = new ResourceAmounts();
|
||||
if (startingResources is not null)
|
||||
stored = new ResourceAmounts
|
||||
{
|
||||
Draft = startingResources.Draft,
|
||||
Food = startingResources.Food,
|
||||
Knowledge = startingResources.Knowledge,
|
||||
Industry = startingResources.Industry,
|
||||
Magic = startingResources.Magic,
|
||||
Gold = startingResources.Gold,
|
||||
Imperial = startingResources.Imperial,
|
||||
Stability = startingResources.Stability
|
||||
};
|
||||
|
||||
if (startingBuildings is not null)
|
||||
foreach (var startingBuilding in startingBuildings)
|
||||
{
|
||||
var entry = new BuildOrderEntry
|
||||
{
|
||||
ItemId = startingBuilding.Id,
|
||||
Name = startingBuilding.Name,
|
||||
RequestedTurn = 1,
|
||||
BuildStartTurn = 1,
|
||||
BuiltFinishTurn = 0,
|
||||
IndustryCostRemaining = 0,
|
||||
Definition = startingBuilding
|
||||
};
|
||||
activeBuildings.Add(entry);
|
||||
result.BuildOrder.Add(entry);
|
||||
}
|
||||
|
||||
var requestsByTurn = buildRequests
|
||||
.OrderBy(r => r.RequestedTurn)
|
||||
.GroupBy(r => r.RequestedTurn)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
var projects = new List<BuildOrderEntry>();
|
||||
|
||||
for (var turn = 1; turn <= totalTurns; turn++)
|
||||
{
|
||||
var incomeThisTurn = new ResourceAmounts();
|
||||
var upkeepThisTurn = new ResourceAmounts();
|
||||
|
||||
foreach (var building in activeBuildings)
|
||||
if (building.BuiltFinishTurn == 0 || turn > building.BuiltFinishTurn)
|
||||
{
|
||||
incomeThisTurn.Add(building.Definition.Income);
|
||||
upkeepThisTurn.Add(building.Definition.Upkeep);
|
||||
}
|
||||
|
||||
stored.Add(incomeThisTurn);
|
||||
stored.Subtract(upkeepThisTurn);
|
||||
|
||||
if (requestsByTurn.TryGetValue(turn, out var requests))
|
||||
foreach (var request in requests)
|
||||
{
|
||||
var nextProject = new BuildOrderEntry
|
||||
{
|
||||
ItemId = request.ItemId,
|
||||
Name = request.Definition.Name,
|
||||
RequestedTurn = turn,
|
||||
BuildStartTurn = turn,
|
||||
BuiltFinishTurn = 0,
|
||||
IndustryCostRemaining = request.Definition.Cost.Industry,
|
||||
Definition = request.Definition
|
||||
};
|
||||
|
||||
stored.Subtract(new ResourceAmounts
|
||||
{
|
||||
Draft = request.Definition.Cost.Draft,
|
||||
Food = request.Definition.Cost.Food,
|
||||
Knowledge = request.Definition.Cost.Knowledge,
|
||||
Magic = request.Definition.Cost.Magic,
|
||||
Gold = request.Definition.Cost.Gold,
|
||||
Imperial = request.Definition.Cost.Imperial,
|
||||
Stability = 0
|
||||
});
|
||||
|
||||
projects.Add(nextProject);
|
||||
result.BuildOrder.Add(nextProject);
|
||||
}
|
||||
|
||||
var availableIndustry = incomeThisTurn.Industry;
|
||||
foreach (var project in projects.Where(p => p.BuiltFinishTurn == 0).OrderBy(p => p.RequestedTurn))
|
||||
{
|
||||
if (availableIndustry <= 0 || project.IndustryCostRemaining <= 0) continue;
|
||||
|
||||
var applied = Math.Min(availableIndustry, project.IndustryCostRemaining);
|
||||
project.IndustryCostRemaining -= applied;
|
||||
availableIndustry -= applied;
|
||||
|
||||
if (project.IndustryCostRemaining <= 0)
|
||||
{
|
||||
project.BuiltFinishTurn = turn;
|
||||
activeBuildings.Add(project);
|
||||
}
|
||||
}
|
||||
|
||||
result.ResourceHistory.Add(new ResourceSnapshot
|
||||
{
|
||||
Turn = turn,
|
||||
Stored = new ResourceAmounts
|
||||
{
|
||||
Draft = stored.Draft,
|
||||
Food = stored.Food,
|
||||
Knowledge = stored.Knowledge,
|
||||
Industry = stored.Industry,
|
||||
Magic = stored.Magic,
|
||||
Gold = stored.Gold,
|
||||
Imperial = stored.Imperial,
|
||||
Stability = stored.Stability
|
||||
},
|
||||
TotalIncome = incomeThisTurn,
|
||||
TotalUpkeep = upkeepThisTurn,
|
||||
Notes = null
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<BuildingDefinition> GetDefaultStartingBuildings()
|
||||
{
|
||||
return new List<BuildingDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = "town-hall-1",
|
||||
Name = "Town Hall I",
|
||||
Description = "Starting civic center that produces basic resources and morale.",
|
||||
SourceId = "General",
|
||||
Income = new ResourceAmounts
|
||||
{
|
||||
Draft = 20,
|
||||
Food = 30,
|
||||
Industry = 20,
|
||||
Gold = 0,
|
||||
Stability = 10
|
||||
},
|
||||
Upkeep = new ResourceAmounts(),
|
||||
Cost = new ResourceAmounts()
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = "throne",
|
||||
Name = "Throne",
|
||||
Description = "Royal treasury building that provides steady gold income.",
|
||||
SourceId = "General",
|
||||
Income = new ResourceAmounts
|
||||
{
|
||||
Gold = 120
|
||||
},
|
||||
Upkeep = new ResourceAmounts(),
|
||||
Cost = new ResourceAmounts()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static BuildPlanResult CreateSampleBuildPlan(int totalTurns = 60)
|
||||
{
|
||||
var sampleRequests = new List<BuildOrderRequest>
|
||||
{
|
||||
new()
|
||||
{
|
||||
ItemId = "farm-1",
|
||||
Definition = new BuildingDefinition
|
||||
{
|
||||
Id = "farm-1",
|
||||
Name = "Farm",
|
||||
Description = "Provides food income and supports future growth.",
|
||||
SourceId = "General",
|
||||
Income = new ResourceAmounts { Food = 15 },
|
||||
Upkeep = new ResourceAmounts { Gold = 2 },
|
||||
Cost = new ResourceAmounts { Gold = 80, Industry = 40 }
|
||||
},
|
||||
RequestedTurn = 2
|
||||
},
|
||||
new()
|
||||
{
|
||||
ItemId = "workshop-1",
|
||||
Definition = new BuildingDefinition
|
||||
{
|
||||
Id = "workshop-1",
|
||||
Name = "Workshop",
|
||||
Description = "Improves production and generates industry.",
|
||||
SourceId = "General",
|
||||
Income = new ResourceAmounts { Industry = 20 },
|
||||
Upkeep = new ResourceAmounts { Gold = 4 },
|
||||
Cost = new ResourceAmounts { Gold = 110, Industry = 80 }
|
||||
},
|
||||
RequestedTurn = 5
|
||||
},
|
||||
new()
|
||||
{
|
||||
ItemId = "market-1",
|
||||
Definition = new BuildingDefinition
|
||||
{
|
||||
Id = "market-1",
|
||||
Name = "Market",
|
||||
Description = "Provides ongoing gold income and supports trade.",
|
||||
SourceId = "General",
|
||||
Income = new ResourceAmounts { Gold = 40 },
|
||||
Upkeep = new ResourceAmounts { Gold = 6 },
|
||||
Cost = new ResourceAmounts { Gold = 180, Industry = 100 }
|
||||
},
|
||||
RequestedTurn = 12
|
||||
}
|
||||
};
|
||||
|
||||
return CalculateBuildPlan(totalTurns, sampleRequests, GetDefaultStartingBuildings(),
|
||||
new ResourceAmounts { Gold = 0 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.8"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.8" PrivateAssets="all"/>
|
||||
<PackageReference Include="MudBlazor" Version="9.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Components\Dialog\ConfirmationDialogComponent.razor" />
|
||||
<AdditionalFiles Include="Components\Dialog\TechStackDialogComponent.razor" />
|
||||
<AdditionalFiles Include="Components\Feedback\ToastComponent.razor" />
|
||||
<AdditionalFiles Include="Components\Inputs\ButtonComponent.razor" />
|
||||
<AdditionalFiles Include="Components\Layout\MainLayout.razor" />
|
||||
<AdditionalFiles Include="Portals\ConfirmationDialogPortal.razor" />
|
||||
<AdditionalFiles Include="Portals\TechStackPortal.razor" />
|
||||
<AdditionalFiles Include="Portals\ToastPortal.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\NotFound.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using WebAssembly
|
||||
@using WebAssembly.Layout
|
||||
@@ -0,0 +1,115 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #0071c1;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
inset: 20vh 0 auto 0;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebAssembly</title>
|
||||
<base href="/" />
|
||||
<link rel="preload" id="webassembly" />
|
||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="WebAssembly.styles.css" rel="stylesheet" />
|
||||
<script type="importmap"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,597 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
: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;
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
: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);
|
||||
}
|
||||
|
||||
hr {
|
||||
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);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
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;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
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;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
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;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
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;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
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;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
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;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user