diff --git a/ProcessImages.cs b/ProcessImages.cs new file mode 100644 index 0000000..657bb73 --- /dev/null +++ b/ProcessImages.cs @@ -0,0 +1,175 @@ +using System; +using System.IO; +using System.Drawing; +using System.Drawing.Imaging; +using System.Collections.Generic; + +public class ImageProcessor +{ + public static void MakeCleanTransparent(string inputPath, string outputPath) + { + using var original = (Bitmap)Image.FromFile(inputPath); + int w = original.Width; + int h = original.Height; + + using var bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); + using (var g = Graphics.FromImage(bmp)) + { + g.DrawImage(original, 0, 0, w, h); + } + + var rect = new Rectangle(0, 0, w, h); + var data = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + + int bytesPerPixel = 4; + int byteCount = data.Stride * h; + byte[] pixels = new byte[byteCount]; + System.Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, byteCount); + + bool[,] isBg = new bool[w, h]; + var queue = new Queue(); + + bool CheckWhite(int x, int y) + { + int idx = y * data.Stride + x * bytesPerPixel; + byte b = pixels[idx]; + byte g = pixels[idx + 1]; + byte r = pixels[idx + 2]; + // Pure/near white or light background + return r > 215 && g > 215 && b > 215 && Math.Abs(r - g) < 25 && Math.Abs(g - b) < 25; + } + + // Seed borders + for (int x = 0; x < w; x++) + { + if (CheckWhite(x, 0)) { isBg[x, 0] = true; queue.Enqueue(new Point(x, 0)); } + if (CheckWhite(x, h - 1)) { isBg[x, h - 1] = true; queue.Enqueue(new Point(x, h - 1)); } + } + for (int y = 0; y < h; y++) + { + if (CheckWhite(0, y)) { isBg[0, y] = true; queue.Enqueue(new Point(0, y)); } + if (CheckWhite(w - 1, y)) { isBg[w - 1, y] = true; queue.Enqueue(new Point(w - 1, y)); } + } + + int[] dx = { 0, 0, 1, -1 }; + int[] dy = { 1, -1, 0, 0 }; + + while (queue.Count > 0) + { + var p = queue.Dequeue(); + for (int i = 0; i < 4; i++) + { + int nx = p.X + dx[i]; + int ny = p.Y + dy[i]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h && !isBg[nx, ny]) + { + if (CheckWhite(nx, ny)) + { + isBg[nx, ny] = true; + queue.Enqueue(new Point(nx, ny)); + } + } + } + } + + // Apply transparency + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int idx = y * data.Stride + x * bytesPerPixel; + if (isBg[x, y]) + { + pixels[idx + 3] = 0; // Alpha = 0 + } + else + { + // Check if adjacent to background for soft anti-aliased edge + bool nearBg = false; + for (int i = 0; i < 4; i++) + { + int nx = x + dx[i]; + int ny = y + dy[i]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h && isBg[nx, ny]) + { + nearBg = true; + break; + } + } + + byte b = pixels[idx]; + byte g = pixels[idx + 1]; + byte r = pixels[idx + 2]; + if (nearBg && r > 210 && g > 210 && b > 210) + { + double avg = (r + g + b) / 3.0; + int alpha = (int)(255.0 * (1.0 - (avg - 210.0) / 45.0)); + pixels[idx + 3] = (byte)Math.Clamp(alpha, 0, 255); + } + } + } + } + + System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, byteCount); + bmp.UnlockBits(data); + + // Ensure output dir + var dir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); + + bmp.Save(outputPath, ImageFormat.Png); + Console.WriteLine($"Exported: {outputPath}"); + } + + public static void Main(string[] args) + { + string brain = @"C:\Users\mvrba\.gemini\antigravity\brain\f097144f-e914-4120-8461-4bdfe0d3cdef"; + string root = @"src\CiselnaVyprava\wwwroot\assets\images"; + + // 1. Knight (Exact same boy character) + string knightIdleSrc = Path.Combine(brain, "disney_hero_knight_1789996129818.jpg"); + string knightAtkSrc = Path.Combine(brain, "knight_action_strike_1789998031553.jpg"); + string knightDefSrc = Path.Combine(brain, "knight_action_shield_1789998042563.jpg"); + string knightDizzySrc = Path.Combine(brain, "hero_knight_dizzy_1789997064946.jpg"); + + // 2. Goblin + string goblinIdleSrc = Path.Combine(brain, "disney_monster_goblin_1789996145106.jpg"); + string goblinAtkSrc = Path.Combine(brain, "goblin_monster_attack_1789997081347.jpg"); + string goblinHurtSrc = Path.Combine(brain, "goblin_monster_hurt_1789997090819.jpg"); + + // 3. Golem Boss + string golemSrc = Path.Combine(brain, "stone_golem_boss_1789997102336.jpg"); + + // Process Knight + for (int p = 1; p <= 4; p++) + { + string idleSrc = (p <= 2) ? knightIdleSrc : knightDizzySrc; + MakeCleanTransparent(idleSrc, Path.Combine(root, $"heroes/knight_idle_p{p}.png")); + MakeCleanTransparent(knightAtkSrc, Path.Combine(root, $"heroes/knight_attack_p{p}.png")); + MakeCleanTransparent(knightDefSrc, Path.Combine(root, $"heroes/knight_defend_p{p}.png")); + MakeCleanTransparent(knightDizzySrc, Path.Combine(root, $"heroes/knight_hurt_p{p}.png")); + } + + // Process Goblin & Slime + for (int p = 1; p <= 4; p++) + { + MakeCleanTransparent(goblinIdleSrc, Path.Combine(root, $"enemies/goblin_idle_p{p}.png")); + MakeCleanTransparent(goblinAtkSrc, Path.Combine(root, $"enemies/goblin_attack_p{p}.png")); + MakeCleanTransparent(goblinHurtSrc, Path.Combine(root, $"enemies/goblin_hurt_p{p}.png")); + + MakeCleanTransparent(goblinIdleSrc, Path.Combine(root, $"enemies/slime_idle_p{p}.png")); + MakeCleanTransparent(goblinAtkSrc, Path.Combine(root, $"enemies/slime_attack_p{p}.png")); + MakeCleanTransparent(goblinHurtSrc, Path.Combine(root, $"enemies/slime_hurt_p{p}.png")); + } + + // Process Golem + for (int p = 1; p <= 4; p++) + { + MakeCleanTransparent(golemSrc, Path.Combine(root, $"enemies/golem_idle_p{p}.png")); + MakeCleanTransparent(golemSrc, Path.Combine(root, $"enemies/golem_attack_p{p}.png")); + MakeCleanTransparent(golemSrc, Path.Combine(root, $"enemies/golem_hurt_p{p}.png")); + } + + Console.WriteLine("All assets converted to clean 32-bit transparent PNGs!"); + } +} diff --git a/src/CiselnaVyprava/Components/BattleArena.razor b/src/CiselnaVyprava/Components/BattleArena.razor new file mode 100644 index 0000000..9621c4f --- /dev/null +++ b/src/CiselnaVyprava/Components/BattleArena.razor @@ -0,0 +1,359 @@ +@inject IBattleEngine BattleEngine +@inject ILocalStorageService Storage +@inject NavigationManager Nav + +
+ +
+ + +
+ @CurrentLevel.Name + @if (CurrentLevel.IsBoss) + { + 👑 BOSS + } +
+
+ 🔥 Kombo: @State.ComboStreak +
+
+ + + @if (!string.IsNullOrEmpty(State.LastFeedbackMessage)) + { + + } + + +
+ + +
+
+ @Hero.Name +
+ @for (int i = 1; i <= State.PlayerMaxHp; i++) + { + + @(i <= State.PlayerCurrentHp ? "❤️" : "🤍") + + } +
+
+ + +
+
Fáza @State.PlayerHpPhase / 4
+ @Hero.Name +
+
+ + +
+
+
@RemainingTimeSeconds.ToString("F1")s
+
@(State.IsDefensePhase ? "OBRANA!" : "ÚTOK")
+
+
+ + +
+
+ @State.Enemy.Name +
+
+ @State.EnemyCurrentHp / @State.EnemyMaxHp HP +
+
+ + +
+
Fáza @State.EnemyHpPhase / 4
+ @State.Enemy.Name +
+
+
+ + +
+ @if (!State.IsBattleOver) + { +
+ + @(State.IsDefensePhase ? "🛡️ OBRANNÝ TEST: Vyber správnu odpoveď!" : "⚔️ VYPOČÍTAJ A ZAÚTOČ:") + +

@State.CurrentProblem.QuestionText

+
+ +
+ @if (State.CurrentProblem.InputType == "MultipleChoice" || State.IsDefensePhase) + { + + } + else + { + + } +
+ } + else + { + +
+

@(State.IsVictory ? "🏆 VÍŤAZSTVO!" : "💀 PORÁŽKA!")

+

@State.LastFeedbackMessage

+
+ @if (State.IsVictory) + { + + } + else + { + + } +
+
+ } +
+
+ +@code { + [Parameter] public Hero Hero { get; set; } = new(); + [Parameter] public Level CurrentLevel { get; set; } = new(); + [Parameter] public GameConfig Config { get; set; } = new(); + [Parameter] public EventCallback OnReturnToMap { get; set; } + [Parameter] public EventCallback OnLevelCompleted { get; set; } + + private BattleState State => BattleEngine.State; + private string CurrentInput { get; set; } = string.Empty; + private System.Threading.Timer? _timer; + private double RemainingTimeSeconds { get; set; } = 6.0; + + protected override void OnInitialized() + { + StartBattle(); + } + + private void StartBattle() + { + BattleEngine.StartBattle(Hero, CurrentLevel, Config); + CurrentInput = string.Empty; + StartTimer(); + } + + private void StartTimer() + { + _timer?.Dispose(); + RemainingTimeSeconds = State.IsDefensePhase ? Config.Battle.DefenseTestDurationSeconds : Config.Battle.StandardTimeSeconds; + _timer = new System.Threading.Timer(async _ => + { + var elapsed = (DateTime.UtcNow - State.ProblemStartTime).TotalSeconds; + double limit = State.IsDefensePhase ? Config.Battle.DefenseTestDurationSeconds : Config.Battle.StandardTimeSeconds; + RemainingTimeSeconds = Math.Max(0, limit - elapsed); + + await InvokeAsync(StateHasChanged); + }, null, 100, 100); + } + + private async Task HandleInputSubmit(string input) + { + if (State.IsBattleOver) return; + + if (State.IsDefensePhase) + { + await BattleEngine.SubmitDefenseAnswerAsync(input); + } + else + { + await BattleEngine.SubmitAnswerAsync(input); + } + + CurrentInput = string.Empty; + await ProcessPostTurn(); + } + + private async Task HandleChoiceSelected(string choice) + { + if (State.IsBattleOver) return; + + if (State.IsDefensePhase) + { + await BattleEngine.SubmitDefenseAnswerAsync(choice); + } + else + { + await BattleEngine.SubmitAnswerAsync(choice); + } + + await ProcessPostTurn(); + } + + private async Task ProcessPostTurn() + { + StateHasChanged(); + + if (!State.IsBattleOver) + { + // Short delay to display attack/hurt animation before next question + await Task.Delay(1000); + if (!State.IsDefensePhase) + { + BattleEngine.NextProblem(); + } + StartTimer(); + StateHasChanged(); + } + else + { + _timer?.Dispose(); + } + } + + private async Task RetryBattle() + { + StartBattle(); + StateHasChanged(); + } + + private async Task OnVictoryContinue() + { + await OnLevelCompleted.InvokeAsync(CurrentLevel); + } + + private async Task GoBackToMap() + { + _timer?.Dispose(); + await OnReturnToMap.InvokeAsync(); + } + + private int EnemyHpPercent => State.EnemyMaxHp > 0 + ? (int)((double)State.EnemyCurrentHp / State.EnemyMaxHp * 100) + : 0; + + private string GetCharacterImage(string folder, int phase, CombatPose pose) + { + string poseName = pose.ToString().ToLowerInvariant(); + return $"{folder}_{poseName}_p{phase}.png"; + } + + private string GetPlayerAnimationClass() => State.PlayerPose switch + { + CombatPose.Attack => "anim-hero-attack", + CombatPose.Defend => "anim-hero-defend", + CombatPose.Hurt => "anim-hero-hurt", + _ => "anim-hero-idle" + }; + + private string GetEnemyAnimationClass() => State.EnemyPose switch + { + CombatPose.Attack => "anim-enemy-attack", + CombatPose.Hurt => "anim-enemy-hurt", + _ => "anim-enemy-idle" + }; + + private string GetTimerColorClass() + { + if (RemainingTimeSeconds > 3.0) return "timer-green"; + if (RemainingTimeSeconds > 1.5) return "timer-yellow"; + return "timer-red"; + } + + private string GetFeedbackClass() => State.LastResult switch + { + CombatResultType.CriticalHit => "feedback-critical", + CombatResultType.StandardHit => "feedback-standard", + CombatResultType.DefenseSuccess => "feedback-defense", + CombatResultType.PlayerMissed or CombatResultType.DefenseFailed => "feedback-error", + _ => "" + }; + + private ElementReference arenaElement; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + try + { + await arenaElement.FocusAsync(); + } + catch { } + } + } + + private async Task HandleKeyDown(KeyboardEventArgs e) + { + if (State.IsBattleOver) + { + if (e.Key == "Enter") + { + if (State.IsVictory) + await OnVictoryContinue(); + else + await RetryBattle(); + } + return; + } + + if (State.CurrentProblem.InputType == "MultipleChoice" || State.IsDefensePhase) + { + // Handle number keys 1, 2, 3 to pick choices + if (int.TryParse(e.Key, out int choiceIndex) && choiceIndex >= 1 && choiceIndex <= State.CurrentProblem.Choices.Count) + { + await HandleChoiceSelected(State.CurrentProblem.Choices[choiceIndex - 1]); + return; + } + + // Handle A/N shortcuts for ÁNO / NIE + if (e.Key.Equals("a", StringComparison.OrdinalIgnoreCase) || e.Key.Equals("y", StringComparison.OrdinalIgnoreCase)) + { + var match = State.CurrentProblem.Choices.FirstOrDefault(c => c.StartsWith("Á", StringComparison.OrdinalIgnoreCase) || c.StartsWith("A", StringComparison.OrdinalIgnoreCase)); + if (match != null) await HandleChoiceSelected(match); + return; + } + if (e.Key.Equals("n", StringComparison.OrdinalIgnoreCase)) + { + var match = State.CurrentProblem.Choices.FirstOrDefault(c => c.StartsWith("N", StringComparison.OrdinalIgnoreCase)); + if (match != null) await HandleChoiceSelected(match); + return; + } + } + else + { + // Direct numeric input (0-9, Backspace, Enter) + if (e.Key.Length == 1 && char.IsDigit(e.Key[0])) + { + if (CurrentInput.Length < 6) + { + CurrentInput += e.Key; + StateHasChanged(); + } + } + else if (e.Key == "Backspace") + { + if (CurrentInput.Length > 0) + { + CurrentInput = CurrentInput.Substring(0, CurrentInput.Length - 1); + StateHasChanged(); + } + } + else if (e.Key == "Enter") + { + if (!string.IsNullOrWhiteSpace(CurrentInput)) + { + await HandleInputSubmit(CurrentInput); + } + } + } + } +} + diff --git a/src/CiselnaVyprava/Components/CharacterSelect.razor b/src/CiselnaVyprava/Components/CharacterSelect.razor new file mode 100644 index 0000000..0ce0526 --- /dev/null +++ b/src/CiselnaVyprava/Components/CharacterSelect.razor @@ -0,0 +1,114 @@ +@inject IGameDataLoader DataLoader +@inject ILocalStorageService Storage + +
+
+

🏰 ČÍSELNÁ VÝPRAVA

+

Záchrana Číselného Kráľovstva

+ + +
+

1. Krok: Vyber si svoj ročník

+
+ + + +
+ +
+ + +
+

2. Krok: Vyber si svojho hrdinu

+
+ @foreach (var hero in DataLoader.Heroes) + { +
+
+ @if (hero.Id == "knight") + { + ⚔️ + } + else if (hero.Id == "mage") + { + 🧙‍♂️ + } + else + { + 🏹 + } +
+

@hero.Name

+ @hero.Title +

@hero.Description

+
+ ✨ Špeciál: @hero.SpecialAttackName +
+
+ } +
+
+ + +
+

3. Krok: Tvoje hrdinské meno

+
+ + +
+
+
+
+ +@code { + [Parameter] public EventCallback OnProfileReady { get; set; } + + private int SelectedGrade { get; set; } = 2; + private string SelectedHeroId { get; set; } = "knight"; + private string HeroName { get; set; } = "Mladý Hrdina"; + + protected override void OnInitialized() + { + if (DataLoader.Heroes.Any()) + { + SelectedHeroId = DataLoader.Heroes.First().Id; + } + } + + private void SetGrade(int grade) + { + SelectedGrade = grade; + } + + private void SelectHero(string heroId) + { + SelectedHeroId = heroId; + } + + private async Task StartQuest() + { + var profile = new PlayerProfile + { + Name = string.IsNullOrWhiteSpace(HeroName) ? "Mladý Hrdina" : HeroName, + SelectedGrade = SelectedGrade, + SelectedHeroId = SelectedHeroId, + CurrentWorldId = 1, + UnlockedLevels = new HashSet { $"{SelectedGrade}-1-1" } + }; + + await Storage.SaveProfileAsync(profile); + await OnProfileReady.InvokeAsync(profile); + } +} diff --git a/src/CiselnaVyprava/Components/ChoiceSelector.razor b/src/CiselnaVyprava/Components/ChoiceSelector.razor new file mode 100644 index 0000000..b6ae7ef --- /dev/null +++ b/src/CiselnaVyprava/Components/ChoiceSelector.razor @@ -0,0 +1,20 @@ +
+
+ @foreach (var choice in Choices) + { + + } +
+
+ +@code { + [Parameter] public List Choices { get; set; } = new(); + [Parameter] public EventCallback OnChoiceSelected { get; set; } + + private async Task SelectChoice(string choice) + { + await OnChoiceSelected.InvokeAsync(choice); + } +} diff --git a/src/CiselnaVyprava/Components/MainMenu.razor b/src/CiselnaVyprava/Components/MainMenu.razor new file mode 100644 index 0000000..91b6511 --- /dev/null +++ b/src/CiselnaVyprava/Components/MainMenu.razor @@ -0,0 +1,71 @@ +@inject ILocalStorageService Storage + + + +@code { + [Parameter] public PlayerProfile Profile { get; set; } = new(); + [Parameter] public EventCallback OnNewGame { get; set; } + [Parameter] public EventCallback OnContinueGame { get; set; } + [Parameter] public EventCallback OnResetGame { get; set; } + + private bool HasSavedGame => Profile.CompletedLevels.Any() || Profile.UnlockedLevels.Count > 1; + + private async Task ContinueGame() + { + await OnContinueGame.InvokeAsync(); + } + + private async Task NewGame() + { + await OnNewGame.InvokeAsync(); + } + + private async Task ConfirmReset() + { + await OnResetGame.InvokeAsync(); + } +} diff --git a/src/CiselnaVyprava/Components/MathKeypad.razor b/src/CiselnaVyprava/Components/MathKeypad.razor new file mode 100644 index 0000000..092b4e7 --- /dev/null +++ b/src/CiselnaVyprava/Components/MathKeypad.razor @@ -0,0 +1,47 @@ +
+
+ @(string.IsNullOrEmpty(CurrentValue) ? "?" : CurrentValue) +
+
+ @for (int i = 1; i <= 9; i++) + { + var digit = i.ToString(); + + } + + + +
+
+ +@code { + [Parameter] public string CurrentValue { get; set; } = string.Empty; + [Parameter] public EventCallback CurrentValueChanged { get; set; } + [Parameter] public EventCallback OnSubmit { get; set; } + + private async Task AppendDigit(string digit) + { + if (CurrentValue.Length < 6) + { + CurrentValue += digit; + await CurrentValueChanged.InvokeAsync(CurrentValue); + } + } + + private async Task Backspace() + { + if (CurrentValue.Length > 0) + { + CurrentValue = CurrentValue.Substring(0, CurrentValue.Length - 1); + await CurrentValueChanged.InvokeAsync(CurrentValue); + } + } + + private async Task Submit() + { + if (!string.IsNullOrWhiteSpace(CurrentValue)) + { + await OnSubmit.InvokeAsync(CurrentValue); + } + } +} diff --git a/src/CiselnaVyprava/Components/WorldMap.razor b/src/CiselnaVyprava/Components/WorldMap.razor new file mode 100644 index 0000000..1e7d6e3 --- /dev/null +++ b/src/CiselnaVyprava/Components/WorldMap.razor @@ -0,0 +1,77 @@ +@inject IGameDataLoader DataLoader + +
+ +
+
+ @(Profile.SelectedHeroId == "knight" ? "⚔️" : Profile.SelectedHeroId == "mage" ? "🧙‍♂️" : "🏹") + @Profile.Name + @(Profile.SelectedGrade). Ročník +
+
+ ⭐ Hviezdy: @Profile.TotalStars +
+ +
+ + + +
+ +@code { + [Parameter] public PlayerProfile Profile { get; set; } = new(); + [Parameter] public EventCallback OnLevelSelected { get; set; } + [Parameter] public EventCallback OnChangeHero { get; set; } + + private List Worlds => DataLoader.GetWorldsForGrade(Profile.SelectedGrade); + + private async Task SelectLevel(Level level) + { + await OnLevelSelected.InvokeAsync(level); + } +} diff --git a/src/CiselnaVyprava/Models/BattleState.cs b/src/CiselnaVyprava/Models/BattleState.cs new file mode 100644 index 0000000..9870006 --- /dev/null +++ b/src/CiselnaVyprava/Models/BattleState.cs @@ -0,0 +1,63 @@ +namespace CiselnaVyprava.Models; + +public enum CombatPose +{ + Idle, + Attack, + Defend, + Hurt +} + +public enum CombatResultType +{ + None, + CriticalHit, + StandardHit, + WeakHit, + PlayerMissed, + DefenseSuccess, + DefenseFailed, + Victory, + Defeat +} + +public class BattleState +{ + public Hero Hero { get; set; } = new(); + public Enemy Enemy { get; set; } = new(); + public Level CurrentLevel { get; set; } = new(); + + public int PlayerCurrentHp { get; set; } + public int PlayerMaxHp { get; set; } + public int EnemyCurrentHp { get; set; } + public int EnemyMaxHp { get; set; } + + public int PlayerHpPhase => CalculateHpPhase(PlayerCurrentHp, PlayerMaxHp); + public int EnemyHpPhase => CalculateHpPhase(EnemyCurrentHp, EnemyMaxHp); + + public CombatPose PlayerPose { get; set; } = CombatPose.Idle; + public CombatPose EnemyPose { get; set; } = CombatPose.Idle; + + public MathProblem CurrentProblem { get; set; } = new(); + public DateTime ProblemStartTime { get; set; } = DateTime.UtcNow; + + public int ComboStreak { get; set; } = 0; + public int SolvedInLevel { get; set; } = 0; + + public bool IsDefensePhase { get; set; } = false; + public bool IsBattleOver { get; set; } = false; + public bool IsVictory { get; set; } = false; + + public CombatResultType LastResult { get; set; } = CombatResultType.None; + public string LastFeedbackMessage { get; set; } = string.Empty; + + public static int CalculateHpPhase(int current, int max) + { + if (max <= 0) return 1; + double pct = (double)current / max * 100.0; + if (pct >= 75.0) return 1; + if (pct >= 50.0) return 2; + if (pct >= 25.0) return 3; + return 4; + } +} diff --git a/src/CiselnaVyprava/Models/Characters.cs b/src/CiselnaVyprava/Models/Characters.cs new file mode 100644 index 0000000..4c91a54 --- /dev/null +++ b/src/CiselnaVyprava/Models/Characters.cs @@ -0,0 +1,23 @@ +namespace CiselnaVyprava.Models; + +public class Hero +{ + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public int BaseHp { get; set; } = 5; + public int AttackPower { get; set; } = 10; + public string AttackName { get; set; } = string.Empty; + public string SpecialAttackName { get; set; } = string.Empty; + public string AssetFolder { get; set; } = string.Empty; +} + +public class Enemy +{ + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public int MaxHp { get; set; } = 30; + public int AttackPower { get; set; } = 1; + public string AssetFolder { get; set; } = string.Empty; +} diff --git a/src/CiselnaVyprava/Models/GameConfig.cs b/src/CiselnaVyprava/Models/GameConfig.cs new file mode 100644 index 0000000..454e9e3 --- /dev/null +++ b/src/CiselnaVyprava/Models/GameConfig.cs @@ -0,0 +1,26 @@ +namespace CiselnaVyprava.Models; + +public class GameConfig +{ + public BattleConfig Battle { get; set; } = new(); + public List VisualPhases { get; set; } = new(); +} + +public class BattleConfig +{ + public double CriticalTimeSeconds { get; set; } = 3.0; + public double StandardTimeSeconds { get; set; } = 6.0; + public double CriticalDamageMultiplier { get; set; } = 1.5; + public double WeakDamageMultiplier { get; set; } = 0.5; + public double DefenseTestDurationSeconds { get; set; } = 5.0; + public int DefaultPlayerHp { get; set; } = 5; + public int ComboStreakBonusDmg { get; set; } = 1; +} + +public class VisualPhaseConfig +{ + public int Phase { get; set; } + public string Name { get; set; } = string.Empty; + public int MinHpPercent { get; set; } + public string Description { get; set; } = string.Empty; +} diff --git a/src/CiselnaVyprava/Models/GameModels.cs b/src/CiselnaVyprava/Models/GameModels.cs new file mode 100644 index 0000000..e97ad7b --- /dev/null +++ b/src/CiselnaVyprava/Models/GameModels.cs @@ -0,0 +1,70 @@ +namespace CiselnaVyprava.Models; + +public class World +{ + public int Id { get; set; } + public int Grade { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string Background { get; set; } = string.Empty; + public List Levels { get; set; } = new(); +} + +public class Level +{ + public string Id { get; set; } = string.Empty; + public int LevelNumber { get; set; } + public string Name { get; set; } = string.Empty; + public bool IsBoss { get; set; } = false; + public Enemy Enemy { get; set; } = new(); + public MathConfig MathConfig { get; set; } = new(); + public int RequiredCorrectAnswers { get; set; } = 4; +} + +public class MathConfig +{ + public List Operations { get; set; } = new(); + public int MinA { get; set; } = 1; + public int MaxA { get; set; } = 10; + public int MinB { get; set; } = 1; + public int MaxB { get; set; } = 10; + public int MaxSum { get; set; } = 20; + public bool AllowCarryOver { get; set; } = true; + public List? Factors { get; set; } + public int MinMultiplier { get; set; } = 1; + public int MaxMultiplier { get; set; } = 10; + public string InputType { get; set; } = "DirectInput"; // DirectInput | MultipleChoice +} + +public class MathProblem +{ + public string QuestionText { get; set; } = string.Empty; + public string DisplayA { get; set; } = string.Empty; + public string Operation { get; set; } = "+"; + public string DisplayB { get; set; } = string.Empty; + public string CorrectAnswer { get; set; } = string.Empty; + public List Choices { get; set; } = new(); + public string InputType { get; set; } = "DirectInput"; + public bool IsDefenseTest { get; set; } = false; +} + +public class PlayerProfile +{ + public string Name { get; set; } = "Mladý Hrdina"; + public int SelectedGrade { get; set; } = 2; + public string SelectedHeroId { get; set; } = "knight"; + public int TotalStars { get; set; } = 0; + public int Gold { get; set; } = 0; + public int CurrentWorldId { get; set; } = 1; + public HashSet UnlockedLevels { get; set; } = new() { "2-1-1", "3-1-1" }; + public HashSet CompletedLevels { get; set; } = new(); + public PlayerStats Stats { get; set; } = new(); +} + +public class PlayerStats +{ + public int TotalSolved { get; set; } + public int CorrectAnswers { get; set; } + public int CriticalHits { get; set; } + public int MaxStreak { get; set; } +} diff --git a/src/CiselnaVyprava/Pages/Home.razor b/src/CiselnaVyprava/Pages/Home.razor index 9001e0b..6991471 100644 --- a/src/CiselnaVyprava/Pages/Home.razor +++ b/src/CiselnaVyprava/Pages/Home.razor @@ -1,7 +1,124 @@ -@page "/" +@page "/" +@inject IGameDataLoader DataLoader +@inject ILocalStorageService Storage -Home +Číselná Výprava - Rozprávková Matematika -

Hello, world!

+
+ @if (IsLoading) + { +
+
+

📜 Načítavam Číselné Kráľovstvo...

+
+
+
+ } + else if (CurrentScreen == ScreenType.MainMenu) + { + + } + else if (CurrentScreen == ScreenType.CharacterSelect) + { + + } + else if (CurrentScreen == ScreenType.WorldMap) + { + + } + else if (CurrentScreen == ScreenType.Battle && SelectedLevel != null) + { + var hero = DataLoader.GetHero(Profile.SelectedHeroId) ?? DataLoader.Heroes.First(); + + } +
-Welcome to your new app. +@code { + private enum ScreenType + { + Loading, + MainMenu, + CharacterSelect, + WorldMap, + Battle + } + + private bool IsLoading { get; set; } = true; + private ScreenType CurrentScreen { get; set; } = ScreenType.Loading; + private PlayerProfile Profile { get; set; } = new(); + private Level? SelectedLevel { get; set; } + + protected override async Task OnInitializedAsync() + { + try + { + await DataLoader.InitializeAsync(); + Profile = await Storage.LoadProfileAsync(); + CurrentScreen = ScreenType.MainMenu; + } + finally + { + IsLoading = false; + } + } + + private async Task HandleResetGame() + { + Profile = new PlayerProfile(); + await Storage.SaveProfileAsync(Profile); + StateHasChanged(); + } + + private async Task HandleProfileReady(PlayerProfile newProfile) + { + Profile = newProfile; + CurrentScreen = ScreenType.WorldMap; + await Storage.SaveProfileAsync(Profile); + } + + private void HandleLevelSelected(Level level) + { + SelectedLevel = level; + CurrentScreen = ScreenType.Battle; + } + + private async Task HandleLevelCompleted(Level completedLevel) + { + Profile.CompletedLevels.Add(completedLevel.Id); + Profile.TotalStars += completedLevel.IsBoss ? 3 : 1; + Profile.Gold += completedLevel.IsBoss ? 50 : 20; + + // Unlock next level in current world + var worlds = DataLoader.GetWorldsForGrade(Profile.SelectedGrade); + var currentWorld = worlds.FirstOrDefault(w => w.Levels.Any(l => l.Id == completedLevel.Id)); + if (currentWorld != null) + { + var currentIndex = currentWorld.Levels.FindIndex(l => l.Id == completedLevel.Id); + if (currentIndex + 1 < currentWorld.Levels.Count) + { + Profile.UnlockedLevels.Add(currentWorld.Levels[currentIndex + 1].Id); + } + else + { + // Unlock first level of next world + var nextWorldIndex = worlds.IndexOf(currentWorld) + 1; + if (nextWorldIndex < worlds.Count && worlds[nextWorldIndex].Levels.Any()) + { + Profile.UnlockedLevels.Add(worlds[nextWorldIndex].Levels.First().Id); + } + } + } + + await Storage.SaveProfileAsync(Profile); + CurrentScreen = ScreenType.WorldMap; + } +} diff --git a/src/CiselnaVyprava/Program.cs b/src/CiselnaVyprava/Program.cs index ca03abe..b7d3892 100644 --- a/src/CiselnaVyprava/Program.cs +++ b/src/CiselnaVyprava/Program.cs @@ -7,5 +7,10 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); await builder.Build().RunAsync(); + diff --git a/src/CiselnaVyprava/Services/BattleEngine.cs b/src/CiselnaVyprava/Services/BattleEngine.cs new file mode 100644 index 0000000..db727d2 --- /dev/null +++ b/src/CiselnaVyprava/Services/BattleEngine.cs @@ -0,0 +1,163 @@ +using CiselnaVyprava.Models; + +namespace CiselnaVyprava.Services; + +public interface IBattleEngine +{ + BattleState State { get; } + void StartBattle(Hero hero, Level level, GameConfig config); + Task SubmitAnswerAsync(string answer); + Task SubmitDefenseAnswerAsync(string answer); + void NextProblem(); +} + +public class BattleEngine : IBattleEngine +{ + private readonly IMathProblemGenerator _mathGenerator; + private GameConfig _config = new(); + + public BattleState State { get; private set; } = new(); + + public BattleEngine(IMathProblemGenerator mathGenerator) + { + _mathGenerator = mathGenerator; + } + + public void StartBattle(Hero hero, Level level, GameConfig config) + { + _config = config; + State = new BattleState + { + Hero = hero, + Enemy = level.Enemy, + CurrentLevel = level, + PlayerMaxHp = hero.BaseHp, + PlayerCurrentHp = hero.BaseHp, + EnemyMaxHp = level.Enemy.MaxHp, + EnemyCurrentHp = level.Enemy.MaxHp, + PlayerPose = CombatPose.Idle, + EnemyPose = CombatPose.Idle, + ComboStreak = 0, + SolvedInLevel = 0, + IsBattleOver = false, + IsVictory = false, + IsDefensePhase = false, + LastResult = CombatResultType.None, + LastFeedbackMessage = string.Empty + }; + + NextProblem(); + } + + public void NextProblem() + { + State.CurrentProblem = _mathGenerator.GenerateProblem(State.CurrentLevel.MathConfig); + State.ProblemStartTime = DateTime.UtcNow; + State.IsDefensePhase = false; + State.PlayerPose = CombatPose.Idle; + State.EnemyPose = CombatPose.Idle; + } + + public async Task SubmitAnswerAsync(string answer) + { + var elapsed = (DateTime.UtcNow - State.ProblemStartTime).TotalSeconds; + bool isCorrect = string.Equals(answer.Trim(), State.CurrentProblem.CorrectAnswer.Trim(), StringComparison.OrdinalIgnoreCase); + + if (isCorrect) + { + State.ComboStreak++; + State.SolvedInLevel++; + + CombatResultType result; + double multiplier; + if (elapsed <= _config.Battle.CriticalTimeSeconds) + { + result = CombatResultType.CriticalHit; + multiplier = _config.Battle.CriticalDamageMultiplier; + State.LastFeedbackMessage = $"⚡ BLESKOVO! ({elapsed:F1}s) - KRITICKÝ ÚDER!"; + } + else if (elapsed <= _config.Battle.StandardTimeSeconds) + { + result = CombatResultType.StandardHit; + multiplier = 1.0; + State.LastFeedbackMessage = $"⚔️ SKVELÝ ZÁSAH! ({elapsed:F1}s)"; + } + else + { + result = CombatResultType.WeakHit; + multiplier = _config.Battle.WeakDamageMultiplier; + State.LastFeedbackMessage = $"✨ ZÁSAH! ({elapsed:F1}s)"; + } + + int baseDmg = State.Hero.AttackPower; + int comboBonus = (State.ComboStreak - 1) * _config.Battle.ComboStreakBonusDmg; + int totalDamage = (int)(baseDmg * multiplier) + comboBonus; + + State.EnemyCurrentHp = Math.Max(0, State.EnemyCurrentHp - totalDamage); + State.PlayerPose = CombatPose.Attack; + State.EnemyPose = CombatPose.Hurt; + State.LastResult = result; + + if (State.EnemyCurrentHp <= 0) + { + State.IsBattleOver = true; + State.IsVictory = true; + State.LastResult = CombatResultType.Victory; + State.LastFeedbackMessage = $"🎉 VÍŤAZSTVO! {State.Enemy.Name} bol porazený!"; + return CombatResultType.Victory; + } + + return result; + } + else + { + // Player made a mistake -> Trigger defense test + State.ComboStreak = 0; + State.PlayerPose = CombatPose.Hurt; + State.EnemyPose = CombatPose.Attack; + State.IsDefensePhase = true; + State.LastResult = CombatResultType.PlayerMissed; + State.LastFeedbackMessage = $"❌ Vedľa! Pozor na protiútok! Aktivuj štít!"; + + State.CurrentProblem = _mathGenerator.GenerateDefenseTest(); + State.ProblemStartTime = DateTime.UtcNow; + + return CombatResultType.PlayerMissed; + } + } + + public async Task SubmitDefenseAnswerAsync(string answer) + { + bool isCorrect = string.Equals(answer.Trim(), State.CurrentProblem.CorrectAnswer.Trim(), StringComparison.OrdinalIgnoreCase); + + if (isCorrect) + { + State.PlayerPose = CombatPose.Defend; + State.EnemyPose = CombatPose.Idle; + State.LastResult = CombatResultType.DefenseSuccess; + State.LastFeedbackMessage = "🛡️ ÚTOK VYKRYTÝ ŠTÍTOM! Bez straty života."; + State.IsDefensePhase = false; + return CombatResultType.DefenseSuccess; + } + else + { + State.PlayerCurrentHp = Math.Max(0, State.PlayerCurrentHp - State.Enemy.AttackPower); + State.PlayerPose = CombatPose.Hurt; + State.EnemyPose = CombatPose.Attack; + State.LastResult = CombatResultType.DefenseFailed; + State.LastFeedbackMessage = $"💥 Zásah! Stratil si 1 život! (Správne bolo: {State.CurrentProblem.CorrectAnswer})"; + State.IsDefensePhase = false; + + if (State.PlayerCurrentHp <= 0) + { + State.IsBattleOver = true; + State.IsVictory = false; + State.LastResult = CombatResultType.Defeat; + State.LastFeedbackMessage = "💔 Nevzdávaj sa! Skús to znova a poraz nepriateľa!"; + return CombatResultType.Defeat; + } + + return CombatResultType.DefenseFailed; + } + } +} diff --git a/src/CiselnaVyprava/Services/GameDataLoader.cs b/src/CiselnaVyprava/Services/GameDataLoader.cs new file mode 100644 index 0000000..3e7e104 --- /dev/null +++ b/src/CiselnaVyprava/Services/GameDataLoader.cs @@ -0,0 +1,63 @@ +using System.Net.Http.Json; +using CiselnaVyprava.Models; + +namespace CiselnaVyprava.Services; + +public interface IGameDataLoader +{ + Task InitializeAsync(); + GameConfig Config { get; } + List Heroes { get; } + List GetWorldsForGrade(int grade); + Hero? GetHero(string id); +} + +public class GameDataLoader : IGameDataLoader +{ + private readonly HttpClient _http; + private GameConfig _config = new(); + private List _heroes = new(); + private List _grade1Worlds = new(); + private List _grade2Worlds = new(); + private List _grade3Worlds = new(); + + public GameDataLoader(HttpClient http) + { + _http = http; + } + + public GameConfig Config => _config; + public List Heroes => _heroes; + + public async Task InitializeAsync() + { + try { _config = await _http.GetFromJsonAsync("data/game-config.json") ?? new(); } catch { _config = new(); } + try { _heroes = await _http.GetFromJsonAsync>("data/heroes.json") ?? new(); } catch { _heroes = new(); } + try { _grade1Worlds = await _http.GetFromJsonAsync>("data/worlds-grade1.json") ?? new(); } catch { _grade1Worlds = new(); } + try { _grade2Worlds = await _http.GetFromJsonAsync>("data/worlds-grade2.json") ?? new(); } catch { _grade2Worlds = new(); } + try { _grade3Worlds = await _http.GetFromJsonAsync>("data/worlds-grade3.json") ?? new(); } catch { _grade3Worlds = new(); } + + if (!_heroes.Any()) + { + _heroes.Add(new Hero { Id = "knight", Name = "Rytier Pravítko", Title = "Ochranca čísiel", Description = "Odvážny rytier s mečom pravdy.", BaseHp = 5, AttackPower = 10, AssetFolder = "assets/images/heroes/knight" }); + } + } + + + public List GetWorldsForGrade(int grade) + { + return grade switch + { + 1 => _grade1Worlds, + 3 => _grade3Worlds, + _ => _grade2Worlds + }; + } + + + public Hero? GetHero(string id) + { + return _heroes.FirstOrDefault(h => h.Id.Equals(id, StringComparison.OrdinalIgnoreCase)) + ?? _heroes.FirstOrDefault(); + } +} diff --git a/src/CiselnaVyprava/Services/LocalStorageService.cs b/src/CiselnaVyprava/Services/LocalStorageService.cs new file mode 100644 index 0000000..25a0ff7 --- /dev/null +++ b/src/CiselnaVyprava/Services/LocalStorageService.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using Microsoft.JSInterop; +using CiselnaVyprava.Models; + +namespace CiselnaVyprava.Services; + +public interface ILocalStorageService +{ + Task LoadProfileAsync(); + Task SaveProfileAsync(PlayerProfile profile); +} + +public class LocalStorageService : ILocalStorageService +{ + private const string ProfileStorageKey = "CiselnaVyprava_PlayerProfile"; + private readonly IJSRuntime _js; + + public LocalStorageService(IJSRuntime js) + { + _js = js; + } + + public async Task LoadProfileAsync() + { + try + { + var json = await _js.InvokeAsync("localStorage.getItem", ProfileStorageKey); + if (!string.IsNullOrWhiteSpace(json)) + { + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var profile = JsonSerializer.Deserialize(json, options); + if (profile != null) return profile; + } + + } + catch + { + // Fallback upon any browser error + } + + return new PlayerProfile(); + } + + public async Task SaveProfileAsync(PlayerProfile profile) + { + try + { + var json = JsonSerializer.Serialize(profile); + await _js.InvokeVoidAsync("localStorage.setItem", ProfileStorageKey, json); + } + catch + { + // Ignored if storage is blocked + } + } +} diff --git a/src/CiselnaVyprava/Services/MathProblemGenerator.cs b/src/CiselnaVyprava/Services/MathProblemGenerator.cs new file mode 100644 index 0000000..9035bc4 --- /dev/null +++ b/src/CiselnaVyprava/Services/MathProblemGenerator.cs @@ -0,0 +1,206 @@ +using CiselnaVyprava.Models; + +namespace CiselnaVyprava.Services; + +public interface IMathProblemGenerator +{ + MathProblem GenerateProblem(MathConfig config); + MathProblem GenerateDefenseTest(); +} + +public class MathProblemGenerator : IMathProblemGenerator +{ + private readonly Random _rand = new(); + + public MathProblem GenerateProblem(MathConfig config) + { + var op = config.Operations[_rand.Next(config.Operations.Count)]; + + return op switch + { + "+" => GenerateAddition(config), + "-" => GenerateSubtraction(config), + "*" => GenerateMultiplication(config), + "/" => GenerateDivision(config), + "<" or ">" or "=" => GenerateComparison(config), + _ => GenerateAddition(config) + }; + } + + private MathProblem GenerateAddition(MathConfig config) + { + int a, b; + if (config.AllowCarryOver) + { + a = _rand.Next(config.MinA, config.MaxA + 1); + b = _rand.Next(config.MinB, Math.Min(config.MaxB, config.MaxSum - a) + 1); + } + else + { + a = _rand.Next(config.MinA, config.MaxA + 1); + b = _rand.Next(config.MinB, config.MaxB + 1); + } + + int sum = a + b; + var problem = new MathProblem + { + DisplayA = a.ToString(), + Operation = "+", + DisplayB = b.ToString(), + CorrectAnswer = sum.ToString(), + QuestionText = $"{a} + {b} = ?", + InputType = config.InputType + }; + + if (config.InputType == "MultipleChoice") + { + problem.Choices = GenerateNumericChoices(sum, 1, config.MaxSum); + } + + return problem; + } + + private MathProblem GenerateSubtraction(MathConfig config) + { + int b = _rand.Next(config.MinB, config.MaxB + 1); + int a = _rand.Next(Math.Max(b, config.MinA), config.MaxA + 1); + int diff = a - b; + + var problem = new MathProblem + { + DisplayA = a.ToString(), + Operation = "-", + DisplayB = b.ToString(), + CorrectAnswer = diff.ToString(), + QuestionText = $"{a} - {b} = ?", + InputType = config.InputType + }; + + if (config.InputType == "MultipleChoice") + { + problem.Choices = GenerateNumericChoices(diff, 0, config.MaxA); + } + + return problem; + } + + private MathProblem GenerateMultiplication(MathConfig config) + { + int factorA; + if (config.Factors != null && config.Factors.Count > 0) + { + factorA = config.Factors[_rand.Next(config.Factors.Count)]; + } + else + { + factorA = _rand.Next(2, 10); + } + + int factorB = _rand.Next(config.MinMultiplier, config.MaxMultiplier + 1); + int product = factorA * factorB; + + var problem = new MathProblem + { + DisplayA = factorA.ToString(), + Operation = "×", + DisplayB = factorB.ToString(), + CorrectAnswer = product.ToString(), + QuestionText = $"{factorA} × {factorB} = ?", + InputType = config.InputType + }; + + if (config.InputType == "MultipleChoice") + { + problem.Choices = GenerateNumericChoices(product, 0, 100); + } + + return problem; + } + + private MathProblem GenerateDivision(MathConfig config) + { + int divisor; + if (config.Factors != null && config.Factors.Count > 0) + { + divisor = config.Factors[_rand.Next(config.Factors.Count)]; + } + else + { + divisor = _rand.Next(2, 10); + } + + int result = _rand.Next(config.MinMultiplier, config.MaxMultiplier + 1); + int dividend = divisor * result; + + var problem = new MathProblem + { + DisplayA = dividend.ToString(), + Operation = "÷", + DisplayB = divisor.ToString(), + CorrectAnswer = result.ToString(), + QuestionText = $"{dividend} ÷ {divisor} = ?", + InputType = config.InputType + }; + + if (config.InputType == "MultipleChoice") + { + problem.Choices = GenerateNumericChoices(result, 1, 10); + } + + return problem; + } + + private MathProblem GenerateComparison(MathConfig config) + { + int a = _rand.Next(config.MinA, config.MaxA + 1); + int b = _rand.Next(config.MinB, config.MaxB + 1); + + string answer = a < b ? "<" : (a > b ? ">" : "="); + + return new MathProblem + { + DisplayA = a.ToString(), + Operation = "?", + DisplayB = b.ToString(), + CorrectAnswer = answer, + QuestionText = $"{a} ? {b}", + InputType = "MultipleChoice", + Choices = new List { "<", "=", ">" } + }; + } + + public MathProblem GenerateDefenseTest() + { + // Simple True/False defense shield challenge + int a = _rand.Next(1, 10); + int b = _rand.Next(1, 10); + bool isCorrect = _rand.Next(2) == 0; + int displayedResult = isCorrect ? (a + b) : (a + b + (_rand.Next(2) == 0 ? 1 : -1)); + if (displayedResult <= 0) displayedResult = a + b + 2; + + return new MathProblem + { + DisplayA = $"{a} + {b} = {displayedResult}", + Operation = "", + DisplayB = "", + CorrectAnswer = isCorrect ? "ÁNO" : "NIE", + QuestionText = $"Je to správne?\n{a} + {b} = {displayedResult}", + InputType = "MultipleChoice", + Choices = new List { "ÁNO", "NIE" }, + IsDefenseTest = true + }; + } + + private List GenerateNumericChoices(int correct, int min, int max) + { + var set = new HashSet { correct }; + while (set.Count < 3) + { + int offset = _rand.Next(-5, 6); + if (offset == 0) offset = 1; + int candidate = Math.Clamp(correct + offset, min, max); + set.Add(candidate); + } + return set.OrderBy(_ => _rand.Next()).Select(x => x.ToString()).ToList(); + } +} diff --git a/src/CiselnaVyprava/_Imports.razor b/src/CiselnaVyprava/_Imports.razor index 9017784..8283fda 100644 --- a/src/CiselnaVyprava/_Imports.razor +++ b/src/CiselnaVyprava/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using System.Net.Http.Json @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @@ -8,3 +8,6 @@ @using Microsoft.JSInterop @using CiselnaVyprava @using CiselnaVyprava.Layout +@using CiselnaVyprava.Models +@using CiselnaVyprava.Services +@using CiselnaVyprava.Components diff --git a/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/title_screen_bg.jpg b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/title_screen_bg.jpg new file mode 100644 index 0000000..3d5c444 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/title_screen_bg.jpg differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world1_meadow.jpg b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world1_meadow.jpg new file mode 100644 index 0000000..313a228 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world1_meadow.jpg differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world2_forest.jpg b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world2_forest.jpg new file mode 100644 index 0000000..313a228 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world2_forest.jpg differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world3_river.jpg b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world3_river.jpg new file mode 100644 index 0000000..313a228 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/backgrounds/world3_river.jpg differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p1.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p2.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p3.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p4.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_attack_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p1.png new file mode 100644 index 0000000..d228789 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p2.png new file mode 100644 index 0000000..d228789 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p3.png new file mode 100644 index 0000000..d228789 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p4.png new file mode 100644 index 0000000..d228789 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_defend_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p1.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p2.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p3.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p4.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_hurt_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p1.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p2.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p3.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p4.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/goblin_idle_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_attack_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_hurt_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/golem_idle_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p1.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p2.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p3.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p4.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_attack_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p1.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p2.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p3.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p4.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_hurt_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p1.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p2.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p3.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p4.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/enemies/slime_idle_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p1.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p2.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p3.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p4.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_attack_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p1.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p2.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p3.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p4.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_defend_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p1.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p2.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p3.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p4.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_hurt_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p1.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p1.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p1.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p2.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p2.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p2.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p3.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p3.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p3.png differ diff --git a/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p4.png b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p4.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/src/CiselnaVyprava/wwwroot/assets/images/heroes/knight_idle_p4.png differ diff --git a/src/CiselnaVyprava/wwwroot/css/app.css b/src/CiselnaVyprava/wwwroot/css/app.css index 3b9a456..e947d87 100644 --- a/src/CiselnaVyprava/wwwroot/css/app.css +++ b/src/CiselnaVyprava/wwwroot/css/app.css @@ -1,89 +1,980 @@ -.valid.modified:not([type=checkbox]) { - outline: 1px solid #26b050; +:root { + --font-main: "Fredoka", "Nunito", cursive, sans-serif; + --color-parchment: #fcf6e8; + --color-parchment-border: #8b5a2b; + --color-gold: #f39c12; + --color-gold-dark: #d68910; + --color-forest: #27ae60; + --color-crimson: #c0392b; + --color-royal-blue: #2980b9; + --shadow-storybook: 0 8px 24px rgba(0, 0, 0, 0.25); } -.invalid { - outline: 1px solid red; +* { + box-sizing: border-box; + user-select: none; } -.validation-message { - color: red; +body, html { + margin: 0; + padding: 0; + font-family: var(--font-main); + background: #2c3e50; + background: radial-gradient(circle, #34495e 0%, #1a252f 100%); + color: #2c3e50; + height: 100%; + overflow-x: hidden; +} + +.game-viewport { + max-width: 1024px; + margin: 0 auto; + min-height: 100vh; + display: flex; + flex-direction: column; + padding: 16px; +} + +/* ======================================================== + CHARACTER SELECT SCREEN + ======================================================== */ +.character-select-screen { + display: flex; + justify-content: center; + align-items: center; + min-height: 90vh; +} + +.parchment-container { + background: var(--color-parchment); + border: 4px solid var(--color-parchment-border); + border-radius: 20px; + box-shadow: var(--shadow-storybook); + padding: 32px; + width: 100%; + max-width: 860px; + text-align: center; +} + +.game-main-title { + font-size: 2.6rem; + color: var(--color-parchment-border); + margin: 0; + text-shadow: 2px 2px 0px rgba(0,0,0,0.1); +} + +.subtitle { + font-size: 1.2rem; + color: #7f8c8d; + margin-top: 4px; + margin-bottom: 24px; +} + +.selection-section { + margin-bottom: 28px; + text-align: left; +} + +.step-title { + font-size: 1.3rem; + color: #34495e; + margin-bottom: 12px; + border-bottom: 2px dashed #d5dbdb; + padding-bottom: 6px; +} + +.grade-toggle-group { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +} + + +.grade-btn { + background: #fff; + border: 3px solid #bdc3c7; + border-radius: 14px; + padding: 16px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + transition: all 0.2s ease; + font-family: var(--font-main); +} + +.grade-btn:hover { + transform: translateY(-2px); + border-color: var(--color-gold); +} + +.grade-btn.active { + border-color: var(--color-gold); + background: #fef9e7; + box-shadow: 0 4px 12px rgba(243, 156, 18, 0.3); +} + +.grade-badge { + font-size: 1.3rem; + font-weight: 700; + color: #2c3e50; +} + +.grade-desc { + font-size: 0.95rem; + color: #7f8c8d; + margin-top: 4px; +} + +.heroes-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; +} + +.hero-card { + background: #fff; + border: 3px solid #bdc3c7; + border-radius: 16px; + padding: 16px; + text-align: center; + cursor: pointer; + transition: all 0.25s ease; +} + +.hero-card:hover { + transform: translateY(-4px); + border-color: var(--color-royal-blue); +} + +.hero-card.selected-card { + border-color: var(--color-gold); + background: #fef9e7; + box-shadow: 0 6px 16px rgba(243, 156, 18, 0.4); +} + +.hero-avatar { + font-size: 3rem; + margin-bottom: 8px; +} + +.hero-name { + margin: 4px 0; + font-size: 1.2rem; + color: #2c3e50; +} + +.hero-title { + font-size: 0.85rem; + color: #95a5a6; + font-weight: 600; +} + +.hero-description { + font-size: 0.85rem; + color: #7f8c8d; + margin: 8px 0; +} + +.hero-skill { + font-size: 0.8rem; + background: #eaeded; + padding: 4px 8px; + border-radius: 8px; + color: #34495e; +} + +.name-field-row { + display: flex; + gap: 12px; +} + +.hero-name-input { + flex: 1; + padding: 14px 18px; + font-size: 1.1rem; + font-family: var(--font-main); + border: 3px solid #bdc3c7; + border-radius: 12px; + outline: none; +} + +.hero-name-input:focus { + border-color: var(--color-royal-blue); +} + +.btn-start-quest { + background: var(--color-forest); + color: white; + font-size: 1.2rem; + font-weight: 700; + font-family: var(--font-main); + border: none; + border-radius: 12px; + padding: 14px 28px; + cursor: pointer; + box-shadow: 0 4px 0 #1e8449; + transition: all 0.15s ease; +} + +.btn-start-quest:hover { + transform: translateY(-2px); + box-shadow: 0 6px 0 #1e8449; +} + +.btn-start-quest:active { + transform: translateY(2px); + box-shadow: 0 2px 0 #1e8449; +} + +/* ======================================================== + WORLD MAP SCREEN + ======================================================== */ +.world-map-screen { + display: flex; + flex-direction: column; + gap: 20px; +} + +.map-top-bar { + background: var(--color-parchment); + border: 3px solid var(--color-parchment-border); + border-radius: 14px; + padding: 12px 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.player-pill { + display: flex; + align-items: center; + gap: 10px; + font-weight: 700; +} + +.grade-tag { + background: var(--color-gold); + color: white; + font-size: 0.8rem; + padding: 2px 8px; + border-radius: 6px; +} + +.btn-change-hero { + background: #ecf0f1; + border: 2px solid #bdc3c7; + border-radius: 8px; + padding: 6px 12px; + font-family: var(--font-main); + cursor: pointer; + font-weight: 600; +} + +.world-card { + background: var(--color-parchment); + border: 4px solid var(--color-parchment-border); + border-radius: 20px; + padding: 24px; + box-shadow: var(--shadow-storybook); +} + +.world-header h2 { + margin: 0; + color: #2c3e50; +} + +.world-header p { + color: #7f8c8d; + margin: 4px 0 16px 0; +} + +.levels-path { + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.level-node { + background: white; + border: 3px solid #bdc3c7; + border-radius: 16px; + padding: 16px 20px; + display: flex; + flex-direction: column; + align-items: center; + min-width: 140px; + cursor: pointer; + font-family: var(--font-main); + transition: all 0.2s ease; +} + +.level-node.unlocked { + border-color: var(--color-gold); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.level-node.unlocked:hover { + transform: translateY(-4px) scale(1.03); +} + +.level-node.completed { + border-color: var(--color-forest); + background: #e8f8f5; +} + +.level-node.boss-node { + border-color: var(--color-crimson); + background: #fdedec; +} + +.node-icon { + font-size: 2rem; + margin-bottom: 6px; +} + +.level-title { + font-weight: 700; + font-size: 0.95rem; +} + +.boss-tag { + background: var(--color-crimson); + color: white; + font-size: 0.7rem; + font-weight: 700; + padding: 2px 6px; + border-radius: 4px; + margin-top: 4px; +} + +/* ======================================================== + BATTLE ARENA + ======================================================== */ +.battle-arena-wrapper { + display: flex; + flex-direction: column; + gap: 16px; +} + +.battle-header { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--color-parchment); + border: 3px solid var(--color-parchment-border); + border-radius: 12px; + padding: 8px 16px; +} + +.btn-back { + background: #ecf0f1; + border: 2px solid #bdc3c7; + border-radius: 8px; + padding: 6px 14px; + font-weight: 700; + cursor: pointer; + font-family: var(--font-main); +} + +.world-title { + font-size: 1.2rem; + font-weight: 700; +} + +.boss-badge { + background: var(--color-crimson); + color: white; + padding: 2px 8px; + border-radius: 6px; + font-size: 0.8rem; + margin-left: 6px; +} + +.combo-counter { + font-size: 1rem; + color: #7f8c8d; +} + +.combo-counter.active-streak { + color: var(--color-gold-dark); + animation: bounce 0.5s infinite alternate; +} + +.feedback-banner { + padding: 10px 16px; + border-radius: 10px; + text-align: center; + font-weight: 700; + font-size: 1.1rem; + color: white; + animation: fadeIn 0.3s ease; +} + +.feedback-critical { background: var(--color-gold); text-shadow: 1px 1px 2px black; } +.feedback-standard { background: var(--color-forest); } +.feedback-defense { background: var(--color-royal-blue); } +.feedback-error { background: var(--color-crimson); } + +/* Combat Stage */ +.arena-stage { + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 20px; + align-items: center; + background: radial-gradient(circle, #5d6d7e 0%, #34495e 100%); + border: 4px solid var(--color-parchment-border); + border-radius: 20px; + padding: 20px; + min-height: 280px; + box-shadow: var(--shadow-storybook); +} + +.combatant { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.combatant-info { + margin-bottom: 10px; +} + +.combatant-name { + color: white; + font-weight: 700; + font-size: 1.2rem; + text-shadow: 1px 1px 3px black; +} + +.hp-hearts { + display: flex; + gap: 4px; + margin-top: 4px; + font-size: 1.4rem; +} + +.hp-bar-wrapper { + width: 140px; + height: 18px; + background: #7f8c8d; + border-radius: 10px; + border: 2px solid white; + position: relative; + overflow: hidden; + margin-top: 4px; +} + +.hp-bar-fill { + height: 100%; + background: #e74c3c; + transition: width 0.3s ease; +} + +.hp-text { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + font-size: 0.75rem; + color: white; + font-weight: 700; + line-height: 16px; +} + +.character-sprite-box { + position: relative; + width: 160px; + height: 200px; + display: flex; + justify-content: center; + align-items: center; +} + +.sprite-phase-indicator { + position: absolute; + top: -10px; + background: rgba(0, 0, 0, 0.6); + color: white; + font-size: 0.75rem; + padding: 2px 8px; + border-radius: 10px; +} + +.character-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + filter: drop-shadow(0 8px 16px rgba(0,0,0,0.35)); +} + + + +.timer-orb { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + border: 4px solid white; + box-shadow: 0 0 15px rgba(255, 255, 255, 0.4); +} + +.timer-green { background: var(--color-forest); } +.timer-yellow { background: var(--color-gold); } +.timer-red { background: var(--color-crimson); animation: pulse 0.5s infinite; } + +.timer-seconds { font-size: 1.2rem; font-weight: 700; } +.timer-label { font-size: 0.7rem; font-weight: 600; } + +/* PARCHMENT ACTION BOARD */ +.parchment-action-board { + background: var(--color-parchment); + border: 4px solid var(--color-parchment-border); + border-radius: 20px; + padding: 20px; + box-shadow: var(--shadow-storybook); + text-align: center; +} + +.equation-box { + margin-bottom: 16px; +} + +.equation-title { + font-size: 1rem; + font-weight: 700; + color: #7f8c8d; +} + +.equation-text { + font-size: 2.8rem; + color: #2c3e50; + margin: 4px 0; + letter-spacing: 2px; +} + +/* KEYPAD */ +.keypad-container { + max-width: 300px; + margin: 0 auto; +} + +.keypad-display { + background: white; + border: 3px solid #bdc3c7; + border-radius: 12px; + padding: 10px; + font-size: 2rem; + font-weight: 700; + margin-bottom: 12px; + min-height: 55px; +} + +.keypad-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} + +.key-btn { + background: white; + border: 3px solid #bdc3c7; + border-radius: 12px; + font-size: 1.5rem; + font-weight: 700; + font-family: var(--font-main); + padding: 12px 0; + cursor: pointer; + box-shadow: 0 4px 0 #bdc3c7; + transition: all 0.1s ease; +} + +.key-btn:active { + transform: translateY(3px); + box-shadow: 0 1px 0 #bdc3c7; +} + +.key-btn.action-key.backspace { background: #fadbd8; border-color: #e74c3c; box-shadow: 0 4px 0 #cb4335; } +.key-btn.action-key.submit { background: #d4efdf; border-color: #27ae60; box-shadow: 0 4px 0 #1e8449; } + +/* CHOICES */ +.choices-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 16px; + max-width: 500px; + margin: 0 auto; +} + +.choice-btn { + background: white; + border: 4px solid var(--color-gold); + border-radius: 16px; + padding: 18px 0; + font-size: 1.8rem; + font-weight: 700; + font-family: var(--font-main); + cursor: pointer; + box-shadow: 0 5px 0 var(--color-gold-dark); + transition: all 0.1s ease; +} + +.choice-btn:hover { + background: #fef9e7; + transform: translateY(-2px); +} + +.choice-btn:active { + transform: translateY(3px); + box-shadow: 0 2px 0 var(--color-gold-dark); +} + +/* VICTORY / DEFEAT CARDS */ +.battle-end-card { + padding: 24px; + border-radius: 16px; +} + +.battle-end-card h2 { font-size: 2.2rem; margin: 0; } +.battle-end-card p { font-size: 1.2rem; margin: 12px 0 24px 0; } + +.victory-card { background: #d4efdf; border: 3px solid #27ae60; color: #1e8449; } +.defeat-card { background: #fadbd8; border: 3px solid #e74c3c; color: #922b21; } + +.action-btn { + font-size: 1.2rem; + font-weight: 700; + font-family: var(--font-main); + padding: 12px 28px; + border: none; + border-radius: 12px; + cursor: pointer; + box-shadow: 0 4px 0 rgba(0,0,0,0.2); +} + +.next-level-btn { background: var(--color-forest); color: white; } +.retry-btn { background: var(--color-crimson); color: white; } + +/* ANIMATIONS */ +@keyframes bounce { + from { transform: translateY(0); } + to { transform: translateY(-4px); } +} + +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.08); } + 100% { transform: scale(1); } +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-8px); } + to { opacity: 1; transform: translateY(0); } +} + +.anim-hero-idle { animation: idleBreathe 3s infinite ease-in-out; } +.anim-enemy-idle { animation: idleBreathe 3s infinite ease-in-out 0.5s; } + +.anim-hero-attack { animation: heroAttackDash 0.8s ease-out; } +.anim-enemy-attack { animation: enemyAttackDash 0.8s ease-out; } +.anim-hero-hurt, .anim-enemy-hurt { animation: hurtShake 0.5s ease-in-out; } + +@keyframes idleBreathe { + 0%, 100% { transform: scale(1) translateY(0); } + 50% { transform: scale(1.02) translateY(-4px); } +} + +@keyframes heroAttackDash { + 0% { transform: translateX(0); } + 30% { transform: translateX(40px) scale(1.1) rotate(5deg); } + 100% { transform: translateX(0); } +} + +@keyframes enemyAttackDash { + 0% { transform: translateX(0); } + 30% { transform: translateX(-40px) scale(1.1) rotate(-5deg); } + 100% { transform: translateX(0); } +} + +@keyframes hurtShake { + 0%, 100% { transform: translateX(0); filter: brightness(1); } + 20%, 60% { transform: translateX(-10px); filter: brightness(1.5) saturate(2); } + 40%, 80% { transform: translateX(10px); } +} + +/* ======================================================== + PC GAME MAIN MENU SCREEN + ======================================================== */ +.viewport-fullscreen { + max-width: 100% !important; + padding: 0 !important; + margin: 0 !important; +} + +.main-menu-fullscreen { + min-height: 100vh; + width: 100vw; + display: flex; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; +} + +.main-menu-overlay { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + min-height: 90vh; + padding: 40px 20px; + z-index: 2; +} + +.title-logo-box { + text-align: center; + margin-top: 20px; + animation: fadeIn 0.8s ease-out; +} + +.title-crown { + font-size: 3.5rem; + filter: drop-shadow(0 4px 10px rgba(0,0,0,0.4)); + animation: bounce 1.5s infinite alternate; +} + +.main-game-title { + font-size: 3.8rem; + font-weight: 900; + color: #fff; + text-shadow: + 3px 3px 0px #8b5a2b, + -3px -3px 0px #8b5a2b, + 3px -3px 0px #8b5a2b, + -3px 3px 0px #8b5a2b, + 0 8px 16px rgba(0,0,0,0.6); + margin: 0; + letter-spacing: 3px; +} + +.title-banner { + display: inline-block; + background: linear-gradient(135deg, #f39c12, #d68910); + color: white; + font-size: 1.3rem; + font-weight: 700; + padding: 6px 28px; + border-radius: 20px; + border: 3px solid #fff; + box-shadow: 0 6px 14px rgba(0,0,0,0.3); + margin-top: 8px; + text-shadow: 1px 1px 2px rgba(0,0,0,0.4); +} + +.menu-actions-parchment { + background: rgba(252, 246, 232, 0.95); + border: 5px solid var(--color-parchment-border); + border-radius: 24px; + padding: 30px 36px; + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + max-width: 480px; + box-shadow: 0 12px 32px rgba(0,0,0,0.4); + backdrop-filter: blur(4px); + animation: fadeIn 1s ease-out; +} + +.pc-menu-btn { + background: #fff; + border: 3px solid #bdc3c7; + border-radius: 16px; + padding: 14px 20px; + display: flex; + align-items: center; + gap: 16px; + cursor: pointer; + font-family: var(--font-main); + transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + text-align: left; + box-shadow: 0 4px 0 #bdc3c7; +} + +.pc-menu-btn:hover { + transform: translateY(-4px) scale(1.02); + border-color: var(--color-gold); +} + +.pc-menu-btn:active { + transform: translateY(2px) scale(0.98); +} + +.pc-menu-btn.new-game-btn { + background: #e8f8f5; + border-color: #27ae60; + box-shadow: 0 5px 0 #1e8449; +} + +.pc-menu-btn.new-game-btn:hover { + background: #d4efdf; + box-shadow: 0 8px 0 #1e8449; +} + +.pc-menu-btn.continue-btn { + background: #fef9e7; + border-color: var(--color-gold); + box-shadow: 0 5px 0 var(--color-gold-dark); +} + +.pc-menu-btn.continue-btn:hover { + background: #fdebd0; + box-shadow: 0 8px 0 var(--color-gold-dark); +} + +.pc-menu-btn.reset-btn { + background: transparent; + border: 2px dashed #e74c3c; + color: #c0392b; + box-shadow: none; + padding: 8px 16px; + justify-content: center; +} + +.pc-menu-btn.reset-btn:hover { + background: #fadbd8; + transform: translateY(-2px); +} + +.btn-icon { + font-size: 2.2rem; +} + +.btn-text-block { + display: flex; + flex-direction: column; +} + +.btn-title { + font-size: 1.4rem; + font-weight: 800; + color: #2c3e50; +} + +.btn-subtitle { + font-size: 0.85rem; + color: #7f8c8d; + margin-top: 2px; +} + +.btn-title-small { + font-size: 0.95rem; + font-weight: 700; +} + +.main-menu-footer { + color: white; + font-weight: 600; + text-shadow: 1px 1px 3px rgba(0,0,0,0.8); + font-size: 1rem; + background: rgba(0, 0, 0, 0.4); + padding: 6px 18px; + border-radius: 12px; +} + +/* ======================================================== + WASM PRE-LOADER & ERROR UI + ======================================================== */ +.wasm-loader-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + width: 100vw; + background: radial-gradient(circle, #34495e 0%, #1a252f 100%); + font-family: var(--font-main, sans-serif); +} + +.wasm-loader-box { + background: #fcf6e8; + border: 4px solid #8b5a2b; + border-radius: 24px; + padding: 36px 48px; + text-align: center; + box-shadow: 0 12px 32px rgba(0,0,0,0.5); + max-width: 420px; + width: 90%; +} + +.wasm-crown { + font-size: 3rem; + animation: bounce 1.2s infinite alternate; +} + +.wasm-title { + font-size: 2.2rem; + color: #8b5a2b; + margin: 8px 0 4px 0; + font-weight: 800; +} + +.wasm-subtitle { + color: #7f8c8d; + font-size: 1rem; + margin-bottom: 20px; +} + +.wasm-progress-bar { + width: 100%; + height: 14px; + background: #e0d7c6; + border-radius: 10px; + overflow: hidden; + border: 2px solid #8b5a2b; + position: relative; +} + +.wasm-progress-fill { + height: 100%; + width: 100%; + background: linear-gradient(90deg, #f39c12, #27ae60, #f39c12); + background-size: 200% 100%; + border-radius: 8px; + animation: progressShimmer 1.5s infinite linear; +} + +@keyframes progressShimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } #blazor-error-ui { - color-scheme: light only; - background: lightyellow; + background: #fadbd8; + color: #922b21; 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; + padding: 0.8rem 1.25rem 0.8rem 1.25rem; position: fixed; width: 100%; - z-index: 1000; + z-index: 10000; + font-family: var(--font-main, sans-serif); + font-weight: 700; } - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; - padding: 1rem 1rem 1rem 3.7rem; - color: white; +#blazor-error-ui .reload { + color: #1e8449; + margin-left: 1rem; + text-decoration: underline; } - .blazor-error-boundary::after { - content: "An error has occurred." - } - -.loading-progress { +#blazor-error-ui .dismiss { + cursor: pointer; position: absolute; - display: block; - width: 8rem; - height: 8rem; - inset: 20vh 0 auto 0; - margin: 0 auto 0 auto; + right: 0.75rem; + top: 0.5rem; } - - .loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); - } - - .loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; - } - -.loading-progress-text { - position: absolute; - text-align: center; - font-weight: bold; - inset: calc(20vh + 3.25rem) 0 auto 0.2rem; -} - - .loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); - } - -code { - color: #c02d76; -} - -.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { - color: var(--bs-secondary-color); - text-align: end; -} - -.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { - text-align: start; -} \ No newline at end of file diff --git a/src/CiselnaVyprava/wwwroot/data/game-config.json b/src/CiselnaVyprava/wwwroot/data/game-config.json new file mode 100644 index 0000000..0a79a25 --- /dev/null +++ b/src/CiselnaVyprava/wwwroot/data/game-config.json @@ -0,0 +1,17 @@ +{ + "battle": { + "criticalTimeSeconds": 3.0, + "standardTimeSeconds": 6.0, + "criticalDamageMultiplier": 1.5, + "weakDamageMultiplier": 0.5, + "defenseTestDurationSeconds": 5.0, + "defaultPlayerHp": 5, + "comboStreakBonusDmg": 1 + }, + "visualPhases": [ + { "phase": 1, "name": "FullHealth", "minHpPercent": 75, "description": "Plná sila a úsmev" }, + { "phase": 2, "name": "Focused", "minHpPercent": 50, "description": "Bojové nasadenie a sústredenie" }, + { "phase": 3, "name": "Fatigued", "minHpPercent": 25, "description": "Zadýchaný, leukoplast" }, + { "phase": 4, "name": "Critical", "minHpPercent": 1, "description": "Kritický stav, motajúce sa hviezdičky" } + ] +} diff --git a/src/CiselnaVyprava/wwwroot/data/heroes.json b/src/CiselnaVyprava/wwwroot/data/heroes.json new file mode 100644 index 0000000..9fbb417 --- /dev/null +++ b/src/CiselnaVyprava/wwwroot/data/heroes.json @@ -0,0 +1,35 @@ +[ + { + "id": "knight", + "name": "Rytier Pravítko", + "title": "Ochranca čísiel", + "description": "Odvážny rytier s mečom pravdy a štítom obrany.", + "baseHp": 5, + "attackPower": 10, + "attackName": "Úder pravítkom", + "specialAttackName": "Kritické seknutie", + "assetFolder": "assets/images/heroes/knight" + }, + { + "id": "mage", + "name": "Mág Číselník", + "title": "Pán matematických kúziel", + "description": "Ovláda bleskovú algebru a mrazivé rovnice.", + "baseHp": 5, + "attackPower": 10, + "attackName": "Ohnivá rovnica", + "specialAttackName": "Bleskový výpočet", + "assetFolder": "assets/images/heroes/mage" + }, + { + "id": "archer", + "name": "Lučostrelec Stovka", + "title": "Majster presnosti", + "description": "Jeho šípy zasahujú presné výsledky na meter presne.", + "baseHp": 5, + "attackPower": 10, + "attackName": "Presný šíp", + "specialAttackName": "Bleskový trojitý výstrel", + "assetFolder": "assets/images/heroes/archer" + } +] diff --git a/src/CiselnaVyprava/wwwroot/data/worlds-grade1.json b/src/CiselnaVyprava/wwwroot/data/worlds-grade1.json new file mode 100644 index 0000000..62684d0 --- /dev/null +++ b/src/CiselnaVyprava/wwwroot/data/worlds-grade1.json @@ -0,0 +1,113 @@ +[ + { + "id": 1, + "grade": 1, + "name": "Rozprávková lúka (Počítanie do 10)", + "description": "Jednoduché sčítanie a odčítanie do 10 pre malých prváčikov.", + "background": "assets/images/backgrounds/world1_meadow.jpg", + "levels": [ + { + "id": "1-1-1", + "levelNumber": 1, + "name": "Lúčny Motýlik", + "enemy": { + "id": "meadow_butterfly", + "name": "Lúčny Motýlik", + "maxHp": 20, + "attackPower": 1, + "assetFolder": "assets/images/enemies/goblin" + }, + "mathConfig": { + "operations": ["+"], + "minA": 1, + "maxA": 5, + "minB": 1, + "maxB": 5, + "maxSum": 10, + "allowCarryOver": false, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 3 + }, + { + "id": "1-1-2", + "levelNumber": 2, + "name": "Zvedavý Zajačik", + "enemy": { + "id": "curious_bunny", + "name": "Zvedavý Zajačik", + "maxHp": 25, + "attackPower": 1, + "assetFolder": "assets/images/enemies/slime" + }, + "mathConfig": { + "operations": ["+", "-"], + "minA": 2, + "maxA": 10, + "minB": 1, + "maxB": 5, + "maxSum": 10, + "allowCarryOver": false, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 3 + }, + { + "id": "1-1-3", + "levelNumber": 3, + "name": "Lesný Medveď (Boss)", + "isBoss": true, + "enemy": { + "id": "gentle_bear_boss", + "name": "Strážca Lúky", + "maxHp": 40, + "attackPower": 1, + "assetFolder": "assets/images/enemies/golem" + }, + "mathConfig": { + "operations": ["+", "-"], + "minA": 3, + "maxA": 10, + "minB": 1, + "maxB": 5, + "maxSum": 10, + "allowCarryOver": false, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 4 + } + ] + }, + { + "id": 2, + "grade": 1, + "name": "Slnečná čistinka (Počítanie do 20 bez prechodu)", + "description": "Sčítanie a odčítanie v obore do 20 (napr. 10 + 4, 15 - 3).", + "background": "assets/images/backgrounds/world2_forest.jpg", + "levels": [ + { + "id": "1-2-1", + "levelNumber": 1, + "name": "Lesný Škriatok", + "enemy": { + "id": "forest_gnome", + "name": "Lesný Škriatok", + "maxHp": 30, + "attackPower": 1, + "assetFolder": "assets/images/enemies/goblin" + }, + "mathConfig": { + "operations": ["+", "-"], + "minA": 10, + "maxA": 19, + "minB": 1, + "maxB": 5, + "maxSum": 20, + "allowCarryOver": false, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 4 + } + ] + } +] diff --git a/src/CiselnaVyprava/wwwroot/data/worlds-grade2.json b/src/CiselnaVyprava/wwwroot/data/worlds-grade2.json new file mode 100644 index 0000000..306c0ae --- /dev/null +++ b/src/CiselnaVyprava/wwwroot/data/worlds-grade2.json @@ -0,0 +1,111 @@ +[ + { + "id": 1, + "grade": 2, + "name": "Lúka sčítania a odčítania", + "description": "Základné sčítanie a odčítanie do 20 s prechodom cez desiatku.", + "background": "assets/images/backgrounds/world1_meadow.jpg", + "levels": [ + { + "id": "2-1-1", + "levelNumber": 1, + "name": "Škriatok Počtárik", + "enemy": { + "id": "goblin_apprentice", + "name": "Škriatok Počtárik", + "maxHp": 30, + "attackPower": 1, + "assetFolder": "assets/images/enemies/goblin" + }, + "mathConfig": { + "operations": ["+"], + "minA": 1, + "maxA": 10, + "minB": 1, + "maxB": 10, + "maxSum": 20, + "allowCarryOver": true, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 3 + }, + { + "id": "2-1-2", + "levelNumber": 2, + "name": "Lesný Slizík", + "enemy": { + "id": "forest_slime", + "name": "Lesný Slizík", + "maxHp": 40, + "attackPower": 1, + "assetFolder": "assets/images/enemies/slime" + }, + "mathConfig": { + "operations": ["+", "-"], + "minA": 5, + "maxA": 20, + "minB": 1, + "maxB": 10, + "maxSum": 20, + "allowCarryOver": true, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 4 + }, + { + "id": "2-1-3", + "levelNumber": 3, + "name": "Kamenný Golem (Boss)", + "isBoss": true, + "enemy": { + "id": "stone_golem_boss", + "name": "Golem Čísiel", + "maxHp": 60, + "attackPower": 1, + "assetFolder": "assets/images/enemies/golem" + }, + "mathConfig": { + "operations": ["+", "-"], + "minA": 8, + "maxA": 20, + "minB": 4, + "maxB": 10, + "maxSum": 20, + "allowCarryOver": true, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 5 + } + ] + }, + { + "id": 2, + "grade": 2, + "name": "Les porovnávania a rovností", + "description": "Porovnávanie čísel pomocou znakov <, >, = a dopĺňanie do desiatky.", + "background": "assets/images/backgrounds/world2_forest.jpg", + "levels": [ + { + "id": "2-2-1", + "levelNumber": 1, + "name": "Múdra Sova", + "enemy": { + "id": "wise_owl", + "name": "Múdra Sova", + "maxHp": 30, + "attackPower": 1, + "assetFolder": "assets/images/enemies/owl" + }, + "mathConfig": { + "operations": ["<", ">", "="], + "minA": 1, + "maxA": 100, + "minB": 1, + "maxB": 100, + "inputType": "MultipleChoice" + }, + "requiredCorrectAnswers": 4 + } + ] + } +] diff --git a/src/CiselnaVyprava/wwwroot/data/worlds-grade3.json b/src/CiselnaVyprava/wwwroot/data/worlds-grade3.json new file mode 100644 index 0000000..23b572e --- /dev/null +++ b/src/CiselnaVyprava/wwwroot/data/worlds-grade3.json @@ -0,0 +1,72 @@ +[ + { + "id": 1, + "grade": 3, + "name": "Rieka malej násobilky (2, 3, 4, 5)", + "description": "Základy násobenia a delenia číslami 2, 3, 4, 5.", + "background": "assets/images/backgrounds/world3_river.jpg", + "levels": [ + { + "id": "3-1-1", + "levelNumber": 1, + "name": "Riečny Škriatok", + "enemy": { + "id": "river_sprite", + "name": "Riečny Škriatok", + "maxHp": 30, + "attackPower": 1, + "assetFolder": "assets/images/enemies/sprite" + }, + "mathConfig": { + "operations": ["*"], + "factors": [2, 3, 4, 5], + "minMultiplier": 1, + "maxMultiplier": 10, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 4 + }, + { + "id": "3-1-2", + "levelNumber": 2, + "name": "Vodník Násobka", + "enemy": { + "id": "water_demon", + "name": "Vodník Násobka", + "maxHp": 40, + "attackPower": 1, + "assetFolder": "assets/images/enemies/water_demon" + }, + "mathConfig": { + "operations": ["*", "/"], + "factors": [2, 3, 4, 5], + "minMultiplier": 1, + "maxMultiplier": 10, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 5 + }, + { + "id": "3-1-3", + "levelNumber": 3, + "name": "Jazerný Drak (Boss)", + "isBoss": true, + "enemy": { + "id": "lake_dragon_boss", + "name": "Jazerný Drak", + "maxHp": 60, + "attackPower": 1, + "assetFolder": "assets/images/enemies/dragon" + }, + "mathConfig": { + "operations": ["*", "/"], + "factors": [2, 3, 4, 5], + "minMultiplier": 1, + "maxMultiplier": 10, + "inputType": "DirectInput" + }, + "requiredCorrectAnswers": 6 + } + ] + } +] diff --git a/src/CiselnaVyprava/wwwroot/index.html b/src/CiselnaVyprava/wwwroot/index.html index 39337a6..d2a35ef 100644 --- a/src/CiselnaVyprava/wwwroot/index.html +++ b/src/CiselnaVyprava/wwwroot/index.html @@ -4,30 +4,39 @@ - CiselnaVyprava + Číselná Výprava + + + - + +
- - - - -
+
+
+
👑
+

ČÍSELNÁ VÝPRAVA

+

Načítavam rozprávkový svet...

+
+
+
+
+
- An unhandled error has occurred. - Reload + Nastala neočakávaná chyba. + Obnoviť hru 🗙
- + + diff --git a/wwwroot/assets/images/enemies/goblin_attack_p1.png b/wwwroot/assets/images/enemies/goblin_attack_p1.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_attack_p1.png differ diff --git a/wwwroot/assets/images/enemies/goblin_attack_p2.png b/wwwroot/assets/images/enemies/goblin_attack_p2.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_attack_p2.png differ diff --git a/wwwroot/assets/images/enemies/goblin_attack_p3.png b/wwwroot/assets/images/enemies/goblin_attack_p3.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_attack_p3.png differ diff --git a/wwwroot/assets/images/enemies/goblin_attack_p4.png b/wwwroot/assets/images/enemies/goblin_attack_p4.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_attack_p4.png differ diff --git a/wwwroot/assets/images/enemies/goblin_hurt_p1.png b/wwwroot/assets/images/enemies/goblin_hurt_p1.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_hurt_p1.png differ diff --git a/wwwroot/assets/images/enemies/goblin_hurt_p2.png b/wwwroot/assets/images/enemies/goblin_hurt_p2.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_hurt_p2.png differ diff --git a/wwwroot/assets/images/enemies/goblin_hurt_p3.png b/wwwroot/assets/images/enemies/goblin_hurt_p3.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_hurt_p3.png differ diff --git a/wwwroot/assets/images/enemies/goblin_hurt_p4.png b/wwwroot/assets/images/enemies/goblin_hurt_p4.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_hurt_p4.png differ diff --git a/wwwroot/assets/images/enemies/goblin_idle_p1.png b/wwwroot/assets/images/enemies/goblin_idle_p1.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_idle_p1.png differ diff --git a/wwwroot/assets/images/enemies/goblin_idle_p2.png b/wwwroot/assets/images/enemies/goblin_idle_p2.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_idle_p2.png differ diff --git a/wwwroot/assets/images/enemies/goblin_idle_p3.png b/wwwroot/assets/images/enemies/goblin_idle_p3.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_idle_p3.png differ diff --git a/wwwroot/assets/images/enemies/goblin_idle_p4.png b/wwwroot/assets/images/enemies/goblin_idle_p4.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/goblin_idle_p4.png differ diff --git a/wwwroot/assets/images/enemies/golem_attack_p1.png b/wwwroot/assets/images/enemies/golem_attack_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_attack_p1.png differ diff --git a/wwwroot/assets/images/enemies/golem_attack_p2.png b/wwwroot/assets/images/enemies/golem_attack_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_attack_p2.png differ diff --git a/wwwroot/assets/images/enemies/golem_attack_p3.png b/wwwroot/assets/images/enemies/golem_attack_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_attack_p3.png differ diff --git a/wwwroot/assets/images/enemies/golem_attack_p4.png b/wwwroot/assets/images/enemies/golem_attack_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_attack_p4.png differ diff --git a/wwwroot/assets/images/enemies/golem_hurt_p1.png b/wwwroot/assets/images/enemies/golem_hurt_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_hurt_p1.png differ diff --git a/wwwroot/assets/images/enemies/golem_hurt_p2.png b/wwwroot/assets/images/enemies/golem_hurt_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_hurt_p2.png differ diff --git a/wwwroot/assets/images/enemies/golem_hurt_p3.png b/wwwroot/assets/images/enemies/golem_hurt_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_hurt_p3.png differ diff --git a/wwwroot/assets/images/enemies/golem_hurt_p4.png b/wwwroot/assets/images/enemies/golem_hurt_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_hurt_p4.png differ diff --git a/wwwroot/assets/images/enemies/golem_idle_p1.png b/wwwroot/assets/images/enemies/golem_idle_p1.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_idle_p1.png differ diff --git a/wwwroot/assets/images/enemies/golem_idle_p2.png b/wwwroot/assets/images/enemies/golem_idle_p2.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_idle_p2.png differ diff --git a/wwwroot/assets/images/enemies/golem_idle_p3.png b/wwwroot/assets/images/enemies/golem_idle_p3.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_idle_p3.png differ diff --git a/wwwroot/assets/images/enemies/golem_idle_p4.png b/wwwroot/assets/images/enemies/golem_idle_p4.png new file mode 100644 index 0000000..b50c142 Binary files /dev/null and b/wwwroot/assets/images/enemies/golem_idle_p4.png differ diff --git a/wwwroot/assets/images/enemies/slime_attack_p1.png b/wwwroot/assets/images/enemies/slime_attack_p1.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_attack_p1.png differ diff --git a/wwwroot/assets/images/enemies/slime_attack_p2.png b/wwwroot/assets/images/enemies/slime_attack_p2.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_attack_p2.png differ diff --git a/wwwroot/assets/images/enemies/slime_attack_p3.png b/wwwroot/assets/images/enemies/slime_attack_p3.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_attack_p3.png differ diff --git a/wwwroot/assets/images/enemies/slime_attack_p4.png b/wwwroot/assets/images/enemies/slime_attack_p4.png new file mode 100644 index 0000000..cb72285 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_attack_p4.png differ diff --git a/wwwroot/assets/images/enemies/slime_hurt_p1.png b/wwwroot/assets/images/enemies/slime_hurt_p1.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_hurt_p1.png differ diff --git a/wwwroot/assets/images/enemies/slime_hurt_p2.png b/wwwroot/assets/images/enemies/slime_hurt_p2.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_hurt_p2.png differ diff --git a/wwwroot/assets/images/enemies/slime_hurt_p3.png b/wwwroot/assets/images/enemies/slime_hurt_p3.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_hurt_p3.png differ diff --git a/wwwroot/assets/images/enemies/slime_hurt_p4.png b/wwwroot/assets/images/enemies/slime_hurt_p4.png new file mode 100644 index 0000000..789ab7e Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_hurt_p4.png differ diff --git a/wwwroot/assets/images/enemies/slime_idle_p1.png b/wwwroot/assets/images/enemies/slime_idle_p1.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_idle_p1.png differ diff --git a/wwwroot/assets/images/enemies/slime_idle_p2.png b/wwwroot/assets/images/enemies/slime_idle_p2.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_idle_p2.png differ diff --git a/wwwroot/assets/images/enemies/slime_idle_p3.png b/wwwroot/assets/images/enemies/slime_idle_p3.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_idle_p3.png differ diff --git a/wwwroot/assets/images/enemies/slime_idle_p4.png b/wwwroot/assets/images/enemies/slime_idle_p4.png new file mode 100644 index 0000000..36bf7b1 Binary files /dev/null and b/wwwroot/assets/images/enemies/slime_idle_p4.png differ diff --git a/wwwroot/assets/images/heroes/knight_attack_p1.png b/wwwroot/assets/images/heroes/knight_attack_p1.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_attack_p1.png differ diff --git a/wwwroot/assets/images/heroes/knight_attack_p2.png b/wwwroot/assets/images/heroes/knight_attack_p2.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_attack_p2.png differ diff --git a/wwwroot/assets/images/heroes/knight_attack_p3.png b/wwwroot/assets/images/heroes/knight_attack_p3.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_attack_p3.png differ diff --git a/wwwroot/assets/images/heroes/knight_attack_p4.png b/wwwroot/assets/images/heroes/knight_attack_p4.png new file mode 100644 index 0000000..6110b71 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_attack_p4.png differ diff --git a/wwwroot/assets/images/heroes/knight_defend_p1.png b/wwwroot/assets/images/heroes/knight_defend_p1.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_defend_p1.png differ diff --git a/wwwroot/assets/images/heroes/knight_defend_p2.png b/wwwroot/assets/images/heroes/knight_defend_p2.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_defend_p2.png differ diff --git a/wwwroot/assets/images/heroes/knight_defend_p3.png b/wwwroot/assets/images/heroes/knight_defend_p3.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_defend_p3.png differ diff --git a/wwwroot/assets/images/heroes/knight_defend_p4.png b/wwwroot/assets/images/heroes/knight_defend_p4.png new file mode 100644 index 0000000..69d38d6 Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_defend_p4.png differ diff --git a/wwwroot/assets/images/heroes/knight_hurt_p1.png b/wwwroot/assets/images/heroes/knight_hurt_p1.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_hurt_p1.png differ diff --git a/wwwroot/assets/images/heroes/knight_hurt_p2.png b/wwwroot/assets/images/heroes/knight_hurt_p2.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_hurt_p2.png differ diff --git a/wwwroot/assets/images/heroes/knight_hurt_p3.png b/wwwroot/assets/images/heroes/knight_hurt_p3.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_hurt_p3.png differ diff --git a/wwwroot/assets/images/heroes/knight_hurt_p4.png b/wwwroot/assets/images/heroes/knight_hurt_p4.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_hurt_p4.png differ diff --git a/wwwroot/assets/images/heroes/knight_idle_p1.png b/wwwroot/assets/images/heroes/knight_idle_p1.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_idle_p1.png differ diff --git a/wwwroot/assets/images/heroes/knight_idle_p2.png b/wwwroot/assets/images/heroes/knight_idle_p2.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_idle_p2.png differ diff --git a/wwwroot/assets/images/heroes/knight_idle_p3.png b/wwwroot/assets/images/heroes/knight_idle_p3.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_idle_p3.png differ diff --git a/wwwroot/assets/images/heroes/knight_idle_p4.png b/wwwroot/assets/images/heroes/knight_idle_p4.png new file mode 100644 index 0000000..7de3dea Binary files /dev/null and b/wwwroot/assets/images/heroes/knight_idle_p4.png differ