This commit is contained in:
2026-05-27 21:07:37 -04:00
parent dd74f9b69f
commit a188e4e338
94 changed files with 0 additions and 70473 deletions
-16
View File
@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.8"/>
</ItemGroup>
</Project>
-5
View File
@@ -1,5 +0,0 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
await builder.Build().RunAsync();
-9
View File
@@ -1,9 +0,0 @@
@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 static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AOW4.Client
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-15
View File
@@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MudBlazor" Version="9.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.8"/>
</ItemGroup>
</Project>
-77
View File
@@ -1,77 +0,0 @@
@using AOW4.Portals
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<ResourcePreloader/>
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]"/>
<link rel="stylesheet" href="@Assets["app.css"]"/>
<link rel="stylesheet" href="@Assets["AOW4.styles.css"]"/>
<ImportMap/>
<link rel="icon" type="image/png" href="favicon.png"/>
<HeadOutlet/>
</head>
<body>
<Routes/>
<script src="@Assets["_framework/blazor.web.js"]"></script>
</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>
@@ -1,118 +0,0 @@
@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();
}
}
@@ -1,125 +0,0 @@
@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();
}
}
@@ -1,102 +0,0 @@
@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);
}
}
}
@@ -1,50 +0,0 @@
<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);
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace AOW4.Components.Inputs;
public enum MyButtonType
{
Primary, // Positive Actions
Secondary // Destruction Action
}
-24
View File
@@ -1,24 +0,0 @@
@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>
@@ -1,98 +0,0 @@
.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;
}
@@ -1,117 +0,0 @@
@page "/building-calculator"
@using System.Text.Json
@using AOW4.Data
<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>
-40
View File
@@ -1,40 +0,0 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that
occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong>
environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter] private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized()
{
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
}
-185
View File
@@ -1,185 +0,0 @@
@page "/"
@using AOW4.Data
<div class="home-container">
<h1>Welcome to AOW4</h1>
<p class="subtitle">Explore our tools and resources</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>
@@ -1,106 +0,0 @@
@page "/references/magic-materials"
@using AOW4.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>
-5
View File
@@ -1,5 +0,0 @@
@page "/not-found"
@layout MainLayout
<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>
@@ -1,75 +0,0 @@
@page "/references/province-improvements"
@using AOW4.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>
-81
View File
@@ -1,81 +0,0 @@
@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
});
}
}
-8
View File
@@ -1,8 +0,0 @@
@using AOW4.Components.Pages
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"
NotFoundPage="typeof(NotFound)">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"/>
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
</Found>
</Router>
-18
View File
@@ -1,18 +0,0 @@
@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 static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AOW4
@using AOW4.Client
@using AOW4.Components
@using AOW4.Components.Layout
@using System.Globalization
@using System.Reflection
@using System.Timers
-41
View File
@@ -1,41 +0,0 @@
namespace AOW4.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; }
}
-329
View File
@@ -1,329 +0,0 @@
namespace AOW4.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
"""
}
};
}
-44
View File
@@ -1,44 +0,0 @@
namespace AOW4.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; }
}
-342
View File
@@ -1,342 +0,0 @@
namespace AOW4.Data;
/// <summary>
/// Static raw data provider for Province Improvements.
/// </summary>
public static class ProvinceImprovementsData
{
public static List<ProvinceImprovement> RawData => new()
{
// Basic Province Improvements
new ProvinceImprovement
{
Name = "Conduit",
Category = "Conduit",
Effects = "+5 Mana",
Requirements = "Mana node, pearl reef, or magic material",
Source = "Base",
CostProduction = 0,
CostGold = 0
},
new ProvinceImprovement
{
Name = "Farm",
Category = "Farm",
Effects = "+5 Food",
Requirements = "Grassland or coast terrain, fungus fields (underground)",
Source = "Base",
CostProduction = 0,
CostGold = 0
},
new ProvinceImprovement
{
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 ProvinceImprovement
{
Name = "Hut",
Category = "Hut",
Effects = "+2 Food",
Requirements = "Ashlands, sand or snow terrain",
Source = "Base",
CostProduction = 0,
CostGold = 0
},
new ProvinceImprovement
{
Name = "Mine",
Category = "Mine",
Effects = "+5 Gold",
Requirements = "Gold vein, iron deposit, or pearl reef resource node",
Source = "Base",
CostProduction = 0,
CostGold = 0
},
new ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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 ProvinceImprovement
{
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
}
};
}
-18
View File
@@ -1,18 +0,0 @@
namespace AOW4.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; }
}
-79
View File
@@ -1,79 +0,0 @@
namespace AOW4.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
}
};
}
-11
View File
@@ -1,11 +0,0 @@
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; } = "";
}
-15
View File
@@ -1,15 +0,0 @@
namespace AOW4.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();
}
-69
View File
@@ -1,69 +0,0 @@
namespace AOW4.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 = []
}
];
}
}
-9
View File
@@ -1,9 +0,0 @@
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";
}
-10
View File
@@ -1,10 +0,0 @@
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;
}
-52
View File
@@ -1,52 +0,0 @@
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.
"""
},
];
}
-9
View File
@@ -1,9 +0,0 @@
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;
}
-48
View File
@@ -1,48 +0,0 @@
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();
}
@@ -1,26 +0,0 @@
@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();
}
}
-26
View File
@@ -1,26 +0,0 @@
@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();
}
}
-63
View File
@@ -1,63 +0,0 @@
@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();
}
}
-44
View File
@@ -1,44 +0,0 @@
using AOW4;
using AOW4.Components;
using AOW4.Services;
using MudBlazor.Services;
using _Imports = AOW4.Client._Imports;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
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.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(_Imports).Assembly);
app.Run();
-25
View File
@@ -1,25 +0,0 @@
{
"$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:5212",
"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:7027;http://localhost:5212",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
-57
View File
@@ -1,57 +0,0 @@
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();
}
}
-88
View File
@@ -1,88 +0,0 @@
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();
}
}
-60
View File
@@ -1,60 +0,0 @@
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();
}
}
-355
View File
@@ -1,355 +0,0 @@
using System.Text.Json.Serialization;
namespace AOW4.Data;
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 });
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
-60
View File
@@ -1,60 +0,0 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.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;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.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."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
.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.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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
-609
View File
@@ -1,609 +0,0 @@
/*!
* 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 */
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
@@ -1,608 +0,0 @@
/*!
* 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-right: 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-right: 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: right;
}
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: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-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;
}
[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.rtl.css.map */
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
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
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
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