initial impl
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import edge_tts
|
||||||
|
|
||||||
|
# Zoznam všetkých unikátnych slov podľa aktualizovaného kánonu PSP
|
||||||
|
words = [
|
||||||
|
# B
|
||||||
|
"by", "aby", "byľ", "bystrý", "Bystrica", "Bytča", "byť", "nábytok", "bývať", "byt",
|
||||||
|
"bydlisko", "príbytok", "dobytok", "kobyla", "obyčaj", "býk", "bylina", "bydlo", "dobyť",
|
||||||
|
"odbyt", "byvol", "bytosť", "bývalý", "úbytok", "prebytok", "zbytočný",
|
||||||
|
# Votrelci B
|
||||||
|
"bič", "bicykel", "bitka", "biela",
|
||||||
|
|
||||||
|
# M
|
||||||
|
"my", "mykať sa", "mýliť sa", "myslieť", "myšlienka", "myseľ", "umývať sa", "mydlo",
|
||||||
|
"myš", "šmýkať sa", "hmyz", "žmýkať", "priemysel", "Myjava", "mýto", "mys", "zamykať",
|
||||||
|
"pomykov", "hmýriť sa", "šmyk", "priesmyk", "omyl", "zmysel", "pomyje",
|
||||||
|
# Votrelci M
|
||||||
|
"miska", "minúta", "milý", "minca",
|
||||||
|
|
||||||
|
# P
|
||||||
|
"pýcha", "pýšiť sa", "pyšný", "prepych", "pykať", "pýr", "pysk", "pytliak", "kopyto",
|
||||||
|
"pýtať sa", "opýtať sa", "pyžamo",
|
||||||
|
# Votrelci P
|
||||||
|
"pivo", "píšťalka", "pilot", "pieseň",
|
||||||
|
|
||||||
|
# R
|
||||||
|
"ryba", "rýchly", "rys", "ryť", "rýpať", "hrýzť", "kryť", "skryť", "koryto", "korytnačka",
|
||||||
|
"strýko", "ryža", "bryndza", "rýdzi", "rýľ", "ryčať", "ryha", "kryha", "poryv", "trýzniť",
|
||||||
|
# Votrelci R
|
||||||
|
"rieka", "ringloty", "rizoto", "ríbezľa",
|
||||||
|
|
||||||
|
# S
|
||||||
|
"syn", "syr", "sýty", "nasýtiť sa", "sypať", "sypký", "syseľ", "syčať", "sýkorka",
|
||||||
|
"sychravý", "osýpky", "vysychať", "sykať",
|
||||||
|
# Votrelci S
|
||||||
|
"sito", "sirup", "sivý", "silný",
|
||||||
|
|
||||||
|
# V
|
||||||
|
"vysoký", "zvyk", "vy", "vykať", "výr", "výskať", "vyť", "vy-", "vyžla", "vydra",
|
||||||
|
"vyhňa", "výsosť", "zvyšok", "výťah",
|
||||||
|
# Votrelci V
|
||||||
|
"vidly", "víla", "víťaz", "vietor",
|
||||||
|
|
||||||
|
# Z
|
||||||
|
"jazyk", "jazykový", "jazyčnica", "nazývať sa", "ozývať sa", "prezývať", "vyzývať", "pozývať",
|
||||||
|
# Votrelci Z
|
||||||
|
"zima", "zips", "zisk", "zrkadlo"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Odstránenie duplicít so zachovaním poradia
|
||||||
|
unique_words = []
|
||||||
|
seen = set()
|
||||||
|
for w in words:
|
||||||
|
lower_w = w.strip().lower()
|
||||||
|
if lower_w not in seen:
|
||||||
|
seen.add(lower_w)
|
||||||
|
unique_words.append(w.strip())
|
||||||
|
|
||||||
|
output_dir = os.path.join("src", "Ypsilonkovo", "wwwroot", "audio", "words")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
VOICE = "sk-SK-ViktoriaNeural"
|
||||||
|
|
||||||
|
async def generate_word(word):
|
||||||
|
hex_name = word.lower().encode("utf-8").hex()
|
||||||
|
file_path = os.path.join(output_dir, f"{hex_name}.mp3")
|
||||||
|
|
||||||
|
# Špeciálna optimalizácia pre krátke slová ako "by"
|
||||||
|
# Ak pošleme text s bodkou, intonácia prirodzene spadne a znie ako krátke "by" a nie "bý"
|
||||||
|
spoken_text = word
|
||||||
|
rate = "-5%" # Jemne pomalšie a pokojnejšie pre deti
|
||||||
|
|
||||||
|
if word.lower() == "by":
|
||||||
|
spoken_text = "by."
|
||||||
|
rate = "+8%" # Krátke, úsečné a jasné 'by' namiesto dlhého 'bý'
|
||||||
|
elif word.lower() == "byť":
|
||||||
|
spoken_text = "byť."
|
||||||
|
rate = "+5%"
|
||||||
|
elif word.lower() == "vy-":
|
||||||
|
spoken_text = "predpona vy."
|
||||||
|
else:
|
||||||
|
spoken_text = f"{word}."
|
||||||
|
|
||||||
|
communicate = edge_tts.Communicate(spoken_text, VOICE, rate=rate)
|
||||||
|
await communicate.save(file_path)
|
||||||
|
print(f"Generated: {hex_name}.mp3")
|
||||||
|
|
||||||
|
async def generate_welcome():
|
||||||
|
welcome_path = os.path.join(output_dir, "welcome_female.mp3")
|
||||||
|
text = "Ahoj! Som víla Ypsilka, tvoja sprievodkyňa kráľovstvom Ypsilonkovo. Poďme sa spolu hravo naučiť vybrané slová!"
|
||||||
|
communicate = edge_tts.Communicate(text, VOICE, rate="-4%")
|
||||||
|
await communicate.save(welcome_path)
|
||||||
|
print("Generated: welcome_female.mp3 with Ypsilka")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print(f"Generating {len(unique_words)} words with {VOICE}...")
|
||||||
|
for w in unique_words:
|
||||||
|
await generate_word(w)
|
||||||
|
await asyncio.sleep(0.05) # Jemný rozostup
|
||||||
|
await generate_welcome()
|
||||||
|
print("All audio files generated successfully!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ypsilonkovo", "Ypsilonkovo\Ypsilonkovo.csproj", "{81F32E05-72D4-4D68-A11F-2BB2B032503E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{81F32E05-72D4-4D68-A11F-2BB2B032503E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="Ypsilonkovo/Ypsilonkovo.csproj" />
|
||||||
|
</Solution>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||||
|
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="alien-game-container">
|
||||||
|
<!-- Horný panel so skóre -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 bg-white p-3 rounded-4 shadow-sm">
|
||||||
|
<div>
|
||||||
|
<h4 class="m-0 fw-bold text-danger">👾 Lov na votrelcov škriatka Mäkkýša: @Category.Letter</h4>
|
||||||
|
<small class="text-muted">Nájdi a odhaľ slovo, ktoré medzi vybrané slová NEPATRÍ!</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<span class="badge bg-warning text-dark fs-6 px-3 py-2 rounded-pill">
|
||||||
|
⭐ Skóre: @score
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-primary fs-6 px-3 py-2 rounded-pill">
|
||||||
|
🎯 Kolo: @(currentRound + 1) / @totalRounds
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!isGameOver)
|
||||||
|
{
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-center bg-white border-0">
|
||||||
|
<div class="d-flex justify-content-center align-items-center gap-2 mb-3">
|
||||||
|
<span class="fs-1">🧙♂️</span>
|
||||||
|
<div>
|
||||||
|
<h5 class="m-0 fw-bold">Škriatok Mäkkýš zamiešal jedného votrelca!</h5>
|
||||||
|
<p class="text-muted m-0">Ktoré slovo sem nepatrí (píše sa s mäkkým i alebo je falošné)?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Kartičky so slovami -->
|
||||||
|
<div class="row g-3 justify-content-center my-3">
|
||||||
|
@foreach (var card in currentRoundCards)
|
||||||
|
{
|
||||||
|
var isSelected = selectedCard == card;
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<button class="btn btn-lg w-100 p-4 rounded-4 shadow-sm text-center transition-transform hover-scale
|
||||||
|
@(isSelected ? (card.IsAlien ? "btn-success" : "btn-danger") : "btn-light border-2")"
|
||||||
|
style="font-size: 1.4rem; font-weight: 700;"
|
||||||
|
disabled="@(selectedCard != null)"
|
||||||
|
@onclick="() => HandleCardClick(card)">
|
||||||
|
<div class="fs-1 mb-2">
|
||||||
|
@(card.IsAlien ? "👽" : "🛡️")
|
||||||
|
</div>
|
||||||
|
<div>@card.Word</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Spätná väzba po výbere -->
|
||||||
|
@if (selectedCard != null)
|
||||||
|
{
|
||||||
|
<div class="mt-4 p-3 rounded-4 @(selectedCard.IsAlien ? "bg-success bg-opacity-10 text-success" : "bg-danger bg-opacity-10 text-danger")">
|
||||||
|
<h5 class="fw-bold m-0">
|
||||||
|
@(selectedCard.IsAlien ? "🎉 Skvelé! Odhalil si votrelca!" : "❌ Pozor, toto je pravé vybrané slovo!")
|
||||||
|
</h5>
|
||||||
|
<p class="mt-1 mb-3">@feedbackMessage</p>
|
||||||
|
<button class="btn btn-lg @(selectedCard.IsAlien ? "btn-success" : "btn-primary") rounded-pill px-5 shadow"
|
||||||
|
@onclick="NextRound">
|
||||||
|
@(currentRound + 1 >= totalRounds ? "Zobraziť vyhodnotenie 🏆" : "Ďalšie kolo ➡️")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Výsledková obrazovka -->
|
||||||
|
<div class="card p-5 rounded-4 shadow text-center bg-white border-0">
|
||||||
|
<span class="fs-1">🏆</span>
|
||||||
|
<h2 class="fw-bold mt-2 text-primary">Lov na votrelcov dokončený!</h2>
|
||||||
|
<p class="fs-5 text-muted">Získal si <strong>@score</strong> bodov z @(totalRounds * 100).</p>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center my-3">
|
||||||
|
@if (score >= totalRounds * 70)
|
||||||
|
{
|
||||||
|
<div class="p-3 bg-warning bg-opacity-25 rounded-4 text-warning-emphasis">
|
||||||
|
<h4>⭐⭐⭐ Získavaš Hviezdu Detektíva! ⭐⭐⭐</h4>
|
||||||
|
<p class="m-0">Škriatok Mäkkýš musel pred tebou zutekať!</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="p-3 bg-light rounded-4">
|
||||||
|
<p class="m-0">Dobrá práca! Precvič si to ešte raz a získaj plný počet hviezd.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center gap-3 mt-4">
|
||||||
|
<button class="btn btn-outline-primary btn-lg rounded-pill px-4" @onclick="RestartGame">
|
||||||
|
🔄 Hrať znova
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary btn-lg rounded-pill px-5 shadow" @onclick="FinishGame">
|
||||||
|
Pokračovať na mapu 🗺️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LetterCategory Category { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback<int> OnCompleted { get; set; }
|
||||||
|
|
||||||
|
private class CardGameItem
|
||||||
|
{
|
||||||
|
public string Word { get; set; } = string.Empty;
|
||||||
|
public bool IsAlien { get; set; }
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int score = 0;
|
||||||
|
private int currentRound = 0;
|
||||||
|
private const int totalRounds = 5;
|
||||||
|
private bool isGameOver = false;
|
||||||
|
|
||||||
|
private List<CardGameItem> currentRoundCards = new();
|
||||||
|
private CardGameItem? selectedCard = null;
|
||||||
|
private string feedbackMessage = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
StartNewGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartNewGame()
|
||||||
|
{
|
||||||
|
score = 0;
|
||||||
|
currentRound = 0;
|
||||||
|
isGameOver = false;
|
||||||
|
PrepareRound();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareRound()
|
||||||
|
{
|
||||||
|
selectedCard = null;
|
||||||
|
feedbackMessage = string.Empty;
|
||||||
|
|
||||||
|
var rnd = new Random();
|
||||||
|
|
||||||
|
// 1 votrelec z AlienPool
|
||||||
|
var aliens = Category.AlienPool.Where(a => a.IsAlien).ToList();
|
||||||
|
var selectedAlien = aliens[rnd.Next(aliens.Count)];
|
||||||
|
|
||||||
|
// 3 poctivé vybrané slová
|
||||||
|
var validWords = Category.Words
|
||||||
|
.OrderBy(_ => rnd.Next())
|
||||||
|
.Take(3)
|
||||||
|
.Select(w => new CardGameItem
|
||||||
|
{
|
||||||
|
Word = w.Word,
|
||||||
|
IsAlien = false,
|
||||||
|
Reason = $"Slovo '{w.Word}' je riadne vybrané slovo po {Category.Letter}."
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var list = new List<CardGameItem>(validWords)
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Word = selectedAlien.Word,
|
||||||
|
IsAlien = true,
|
||||||
|
Reason = selectedAlien.Reason
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
currentRoundCards = list.OrderBy(_ => rnd.Next()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleCardClick(CardGameItem card)
|
||||||
|
{
|
||||||
|
selectedCard = card;
|
||||||
|
feedbackMessage = card.Reason;
|
||||||
|
|
||||||
|
if (card.IsAlien)
|
||||||
|
{
|
||||||
|
score += 100;
|
||||||
|
await Audio.PlaySuccessAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Audio.PlayWrongAsync();
|
||||||
|
}
|
||||||
|
await Audio.SpeakWordAsync(card.Word);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextRound()
|
||||||
|
{
|
||||||
|
currentRound++;
|
||||||
|
if (currentRound >= totalRounds)
|
||||||
|
{
|
||||||
|
isGameOver = true;
|
||||||
|
if (score >= totalRounds * 70)
|
||||||
|
{
|
||||||
|
_ = Audio.PlayFanfareAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrepareRound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartGame()
|
||||||
|
{
|
||||||
|
StartNewGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FinishGame()
|
||||||
|
{
|
||||||
|
int stars = score >= 400 ? 1 : 0;
|
||||||
|
await OnCompleted.InvokeAsync(stars);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="boss-container">
|
||||||
|
<!-- Horný panel súboja -->
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm mb-4 border-0 text-white"
|
||||||
|
style="background: linear-gradient(135deg, #475569, #0f172a);">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3">
|
||||||
|
<!-- Hráč -->
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="fs-1 bg-primary rounded-circle p-2 px-3 shadow">🛡️</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold m-0">Strážca Ypsilonu</h5>
|
||||||
|
<div class="d-flex gap-1 fs-5 mt-1">
|
||||||
|
@for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
<span>@(i < playerHp ? "❤️" : "🖤")</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<span class="badge bg-danger fs-5 px-4 py-2 rounded-pill shadow animate-pulse">
|
||||||
|
⚡ FINÁLNY SÚBOJ: PROVINCIA @Category.Letter ⚡
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Škriatok Mäkkýš -->
|
||||||
|
<div class="d-flex align-items-center gap-3 text-end">
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold m-0 text-warning">Škriatok Mäkkýš</h5>
|
||||||
|
<div class="progress mt-1 shadow-sm" style="width: 140px; height: 16px; background-color: rgba(255,255,255,0.2);">
|
||||||
|
<div class="progress-bar bg-danger" style="width: @(bossHp * 20)%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fs-1 bg-danger rounded-circle p-2 px-3 shadow">🧙♂️</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!isBossDefeated && playerHp > 0)
|
||||||
|
{
|
||||||
|
var q = questions[currentQIndex];
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-center bg-white border-0">
|
||||||
|
<div class="mb-4">
|
||||||
|
<span class="badge bg-secondary px-3 py-1 rounded-pill mb-2">Otázka @(currentQIndex + 1) z @questions.Count</span>
|
||||||
|
<h3 class="fw-bold text-dark mt-2">@q.QuestionText</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 justify-content-center">
|
||||||
|
@foreach (var opt in q.Options)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<button class="btn btn-lg w-100 p-4 rounded-4 shadow-sm text-center fw-bold transition-transform hover-scale
|
||||||
|
@(isAnswered ? (opt == q.CorrectOption ? "btn-success" : (opt == selectedOption ? "btn-danger" : "btn-light")) : "btn-light border-2")"
|
||||||
|
style="font-size: 1.25rem;"
|
||||||
|
disabled="@isAnswered"
|
||||||
|
@onclick="() => CheckAnswer(opt)">
|
||||||
|
@opt
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (isAnswered)
|
||||||
|
{
|
||||||
|
<div class="mt-4 p-3 rounded-4 @(isCorrect ? "bg-success bg-opacity-10 text-success" : "bg-danger bg-opacity-10 text-danger")">
|
||||||
|
<h5 class="fw-bold m-0">@(isCorrect ? "💥 Zásah! Škriatok stratil život!" : "🛡️ Jáj! Škriatok ťa prekabátil!")</h5>
|
||||||
|
<p class="m-0 mt-1">@q.Explanation</p>
|
||||||
|
|
||||||
|
<button class="btn btn-lg @(isCorrect ? "btn-success" : "btn-primary") rounded-pill px-5 shadow mt-3"
|
||||||
|
@onclick="NextQuestion">
|
||||||
|
Ďalej ➡️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (isBossDefeated)
|
||||||
|
{
|
||||||
|
<!-- Víťazstvo nad bossom -->
|
||||||
|
<div class="card p-5 rounded-4 shadow text-center bg-white border-0">
|
||||||
|
<span class="fs-1">👑</span>
|
||||||
|
<h1 class="fw-bold text-success mt-2">Škriatok Mäkkýš bol porazený!</h1>
|
||||||
|
<p class="fs-5 text-muted">
|
||||||
|
Oslobodil si provinciu <strong>@Category.Title</strong> a získal Zlatú Pečať!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="my-4 p-4 rounded-4 bg-warning bg-opacity-25 d-inline-block">
|
||||||
|
<span class="fs-1">🎖️</span>
|
||||||
|
<h3 class="fw-bold text-dark m-0">Zlatá Pečať Ypsilonu (@Category.Letter)</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<button class="btn btn-lg btn-success rounded-pill px-5 shadow" @onclick="FinishBoss">
|
||||||
|
Návrat na mapu kráľovstva 🗺️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Prehra hráča -->
|
||||||
|
<div class="card p-5 rounded-4 shadow text-center bg-white border-0">
|
||||||
|
<span class="fs-1">💔</span>
|
||||||
|
<h2 class="fw-bold text-danger mt-2">Škriatok Mäkkýš ťa tentoraz premohol!</h2>
|
||||||
|
<p class="fs-5 text-muted">
|
||||||
|
Nevadí! Zopakuj si vláčik slov a hádanky a skús vyzvať škriatka znova.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<button class="btn btn-lg btn-primary rounded-pill px-5 shadow" @onclick="RestartBoss">
|
||||||
|
🔄 Skúsiť znova
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LetterCategory Category { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback<bool> OnDefeated { get; set; }
|
||||||
|
|
||||||
|
private class BossQuestion
|
||||||
|
{
|
||||||
|
public string QuestionText { get; set; } = string.Empty;
|
||||||
|
public List<string> Options { get; set; } = new();
|
||||||
|
public string CorrectOption { get; set; } = string.Empty;
|
||||||
|
public string Explanation { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int playerHp = 3;
|
||||||
|
private int bossHp = 5;
|
||||||
|
private int currentQIndex = 0;
|
||||||
|
private bool isBossDefeated = false;
|
||||||
|
private bool isAnswered = false;
|
||||||
|
private bool isCorrect = false;
|
||||||
|
private string? selectedOption = null;
|
||||||
|
private List<BossQuestion> questions = new();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
RestartBoss();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartBoss()
|
||||||
|
{
|
||||||
|
playerHp = 3;
|
||||||
|
bossHp = 5;
|
||||||
|
currentQIndex = 0;
|
||||||
|
isBossDefeated = false;
|
||||||
|
isAnswered = false;
|
||||||
|
selectedOption = null;
|
||||||
|
BuildQuestions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildQuestions()
|
||||||
|
{
|
||||||
|
questions.Clear();
|
||||||
|
var rnd = new Random();
|
||||||
|
var words = Category.Words.OrderBy(_ => rnd.Next()).ToList();
|
||||||
|
|
||||||
|
// 1. Doplň chýbajúce slovo v reťazci
|
||||||
|
if (Category.Words.Count >= 3)
|
||||||
|
{
|
||||||
|
var w1 = Category.Words[0].Word;
|
||||||
|
var w2 = Category.Words[1].Word;
|
||||||
|
var w3 = Category.Words[2].Word;
|
||||||
|
questions.Add(new BossQuestion
|
||||||
|
{
|
||||||
|
QuestionText = $"Ktoré slovo nasleduje za sebou: „{w1}“, „{w2}“, [ ??? ] ?",
|
||||||
|
Options = new List<string> { w3, words.Last().Word, "bicykel" }.OrderBy(_ => rnd.Next()).ToList(),
|
||||||
|
CorrectOption = w3,
|
||||||
|
Explanation = $"Presne tak! Rad pokračuje slovom '{w3}'."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Chyť votrelca
|
||||||
|
var alien = Category.AlienPool.FirstOrDefault(a => a.IsAlien)?.Word ?? "bicykel";
|
||||||
|
var validW = words.First().Word;
|
||||||
|
var validW2 = words.Skip(1).First().Word;
|
||||||
|
questions.Add(new BossQuestion
|
||||||
|
{
|
||||||
|
QuestionText = "Ktoré z týchto slov je votrelec a NIE JE vybrané slovo?",
|
||||||
|
Options = new List<string> { alien, validW, validW2 }.OrderBy(_ => rnd.Next()).ToList(),
|
||||||
|
CorrectOption = alien,
|
||||||
|
Explanation = $"Výborne! Slovo '{alien}' nepatrí medzi vybrané slová."
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Význam slova
|
||||||
|
var targetWord = words.FirstOrDefault(w => w.IsArchaicOrDifficult) ?? words.First();
|
||||||
|
var otherWord = words.FirstOrDefault(w => w.Word != targetWord.Word) ?? words.Last();
|
||||||
|
questions.Add(new BossQuestion
|
||||||
|
{
|
||||||
|
QuestionText = $"Čo znamená vybrané slovo „{targetWord.Word}“?",
|
||||||
|
Options = new List<string> { targetWord.Meaning, otherWord.Meaning, "Žlté auto na preteky" }.OrderBy(_ => rnd.Next()).ToList(),
|
||||||
|
CorrectOption = targetWord.Meaning,
|
||||||
|
Explanation = $"Správne! {targetWord.Word} znamená: {targetWord.Meaning}."
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Doplnenie do vety
|
||||||
|
var sentenceWord = words.Skip(2).FirstOrDefault() ?? words.First();
|
||||||
|
var sentence = sentenceWord.ExampleSentence.Replace(sentenceWord.Word, "______", StringComparison.OrdinalIgnoreCase);
|
||||||
|
questions.Add(new BossQuestion
|
||||||
|
{
|
||||||
|
QuestionText = $"Doplň správne slovo do vety: „{sentence}“",
|
||||||
|
Options = new List<string> { sentenceWord.Word, words.Last().Word, "slnko" }.OrderBy(_ => rnd.Next()).ToList(),
|
||||||
|
CorrectOption = sentenceWord.Word,
|
||||||
|
Explanation = $"Skvelá pamäť! Patrí tam slovo '{sentenceWord.Word}'."
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Finálna otázka
|
||||||
|
var lastWord = words.Last();
|
||||||
|
questions.Add(new BossQuestion
|
||||||
|
{
|
||||||
|
QuestionText = $"Aké písmeno po {Category.Letter} sa píše v slove „{lastWord.Word}“?",
|
||||||
|
Options = new List<string> { "Tvrdé Y/Ý", "Mäkké I/Í", "Dvojhláska Ô" }.OrderBy(_ => rnd.Next()).ToList(),
|
||||||
|
CorrectOption = "Tvrdé Y/Ý",
|
||||||
|
Explanation = $"Presne tak! Vybrané slová sa vždy píšu s tvrdým Y/Ý."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckAnswer(string opt)
|
||||||
|
{
|
||||||
|
isAnswered = true;
|
||||||
|
selectedOption = opt;
|
||||||
|
var q = questions[currentQIndex];
|
||||||
|
|
||||||
|
if (opt == q.CorrectOption)
|
||||||
|
{
|
||||||
|
isCorrect = true;
|
||||||
|
bossHp--;
|
||||||
|
await Audio.PlaySuccessAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isCorrect = false;
|
||||||
|
playerHp--;
|
||||||
|
await Audio.PlayWrongAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NextQuestion()
|
||||||
|
{
|
||||||
|
isAnswered = false;
|
||||||
|
selectedOption = null;
|
||||||
|
|
||||||
|
if (bossHp <= 0)
|
||||||
|
{
|
||||||
|
isBossDefeated = true;
|
||||||
|
await Audio.PlayFanfareAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerHp <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentQIndex++;
|
||||||
|
if (currentQIndex >= questions.Count)
|
||||||
|
{
|
||||||
|
// Ak prešiel všetky otázky a prežil, boss padol
|
||||||
|
isBossDefeated = true;
|
||||||
|
await Audio.PlayFanfareAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FinishBoss()
|
||||||
|
{
|
||||||
|
await OnDefeated.InvokeAsync(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||||
|
<div class="modal-content rounded-4 border-0 shadow-lg">
|
||||||
|
<div class="modal-header border-0 text-white rounded-top-4"
|
||||||
|
style="background: @Category.BgGradient;">
|
||||||
|
<h4 class="modal-title fw-bold">
|
||||||
|
📖 Kniha múdrosti: Vybrané slová po @Category.Letter
|
||||||
|
</h4>
|
||||||
|
<button type="button" class="btn-close btn-close-white" @onclick="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4 bg-light">
|
||||||
|
<p class="text-muted">
|
||||||
|
Klikni na ktorékoľvek slovo, aby si počul správnu výslovnosť v slovenčine a videl príklad vo vete.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
@foreach (var word in Category.Words)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<div class="card p-3 rounded-4 shadow-sm border-0 bg-white h-100 cursor-pointer hover-scale transition-all"
|
||||||
|
@onclick="() => SpeakAndHighlight(word)">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="fs-2">@word.Emoji</span>
|
||||||
|
<span class="fs-4 fw-bold text-dark">@word.Word</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-outline-primary rounded-circle p-2" title="Prehrať hlas">
|
||||||
|
🔊
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="text-primary fw-medium small mb-1">@word.Meaning</div>
|
||||||
|
<div class="text-muted small fst-italic">„@word.ExampleSentence“</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 bg-light rounded-bottom-4">
|
||||||
|
<button type="button" class="btn btn-primary rounded-pill px-4" @onclick="Close">
|
||||||
|
Rozumiem, poďme hrať! 🚀
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LetterCategory Category { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback OnClose { get; set; }
|
||||||
|
|
||||||
|
private async Task SpeakAndHighlight(WordItem word)
|
||||||
|
{
|
||||||
|
await Audio.SpeakWordAsync(word.Word);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Close()
|
||||||
|
{
|
||||||
|
await OnClose.InvokeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="train-game-container">
|
||||||
|
<!-- Horná lišta: Výber fázy a skupiny -->
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3 bg-white p-3 rounded-4 shadow-sm">
|
||||||
|
<div>
|
||||||
|
<h4 class="m-0 fw-bold text-primary">🚂 Vláčik slov & Most: @Category.Title</h4>
|
||||||
|
<small class="text-muted">Postupné memorovanie poradia radu vybraných slov</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 align-items-center mt-2 mt-md-0">
|
||||||
|
<span class="fw-bold me-1">Dávka slov:</span>
|
||||||
|
@for (int g = 1; g <= MaxChunkGroup; g++)
|
||||||
|
{
|
||||||
|
var groupNum = g;
|
||||||
|
<button class="btn btn-sm @(selectedChunkGroup == groupNum ? "btn-primary shadow" : "btn-outline-primary") rounded-pill px-3"
|
||||||
|
@onclick="() => SelectChunk(groupNum)">
|
||||||
|
Časť @groupNum (@GetGroupWords(groupNum).Count slov)
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button class="btn btn-sm @(selectedChunkGroup == -1 ? "btn-warning text-dark fw-bold shadow" : "btn-outline-warning text-dark") rounded-pill px-3"
|
||||||
|
@onclick="() => SelectChunk(-1)">
|
||||||
|
🌟 Celý rad (@Category.Words.Count)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Výber herného režimu / Fázy -->
|
||||||
|
<div class="d-flex justify-content-center gap-2 mb-4">
|
||||||
|
<button class="btn @(currentPhase == Phase.Learn ? "btn-success" : "btn-light") rounded-pill px-3 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetPhase(Phase.Learn)">
|
||||||
|
👂 1. Počúvaj & Rytmus
|
||||||
|
</button>
|
||||||
|
<button class="btn @(currentPhase == Phase.MissingWord ? "btn-success" : "btn-light") rounded-pill px-3 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetPhase(Phase.MissingWord)">
|
||||||
|
🧩 2. Doplň chýbajúci vagón
|
||||||
|
</button>
|
||||||
|
<button class="btn @(currentPhase == Phase.AssembleTrain ? "btn-success" : "btn-light") rounded-pill px-3 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetPhase(Phase.AssembleTrain)">
|
||||||
|
🔨 3. Poskladaj celý vlak
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@switch (currentPhase)
|
||||||
|
{
|
||||||
|
case Phase.Learn:
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-center bg-white border-0">
|
||||||
|
<h5 class="fw-bold mb-1">Počúvaj slová po poradí</h5>
|
||||||
|
<p class="text-muted">Klikni na tlačidlo alebo priamo na vagónik, aby ti ho slovenský hlas zreteľne prečítal.</p>
|
||||||
|
|
||||||
|
<!-- Vláčik s vagónikmi -->
|
||||||
|
<div class="train-track py-4 d-flex flex-wrap justify-content-center align-items-center gap-3">
|
||||||
|
<div class="locomotive p-3 rounded-4 shadow-sm bg-danger text-white fw-bold d-flex flex-column align-items-center">
|
||||||
|
<span class="fs-1">🚂</span>
|
||||||
|
<span>Y-EXPRES</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@for (int i = 0; i < ActiveWords.Count; i++)
|
||||||
|
{
|
||||||
|
var word = ActiveWords[i];
|
||||||
|
var isHighlighted = highlightedIndex == i;
|
||||||
|
<div class="wagon card p-3 rounded-4 shadow-sm cursor-pointer transition-all @(isHighlighted ? "wagon-active" : "")"
|
||||||
|
style="min-width: 110px; border-top: 5px solid @Category.ThemeColor;"
|
||||||
|
@onclick="() => PlaySingleWord(word)">
|
||||||
|
<span class="fs-2 mb-1">@word.Emoji</span>
|
||||||
|
<span class="fw-bold fs-5 text-dark">@word.Word</span>
|
||||||
|
<small class="text-muted mt-1" style="font-size: 0.75rem;">@word.Meaning</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<button class="btn btn-lg btn-primary rounded-pill px-4 shadow"
|
||||||
|
disabled="@isPlayingRhythm"
|
||||||
|
@onclick="PlayFullRhythm">
|
||||||
|
@(isPlayingRhythm ? "🔊 Predčítavam celý rad..." : "▶️ Prečítaj celý rad za sebou")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Phase.MissingWord:
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-center bg-white border-0">
|
||||||
|
<h5 class="fw-bold mb-1">Ktoré slovo nasleduje v rade?</h5>
|
||||||
|
<p class="text-muted">Jeden vagónik sa stratil! Pozri sa na predchádzajúce slová a doplň ten správny.</p>
|
||||||
|
|
||||||
|
<!-- Vláčik s chýbajúcim článkom -->
|
||||||
|
<div class="train-track py-3 d-flex flex-wrap justify-content-center align-items-center gap-3">
|
||||||
|
<div class="locomotive p-2 rounded-4 shadow-sm bg-danger text-white fw-bold">
|
||||||
|
<span class="fs-2">🚂</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@for (int i = 0; i < ActiveWords.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
var word = ActiveWords[index];
|
||||||
|
|
||||||
|
if (index == missingTargetIndex)
|
||||||
|
{
|
||||||
|
<div class="wagon-slot card p-3 rounded-4 shadow-sm border-dashed text-primary fw-bold"
|
||||||
|
style="min-width: 120px; min-height: 100px; border: 3px dashed #3b82f6; background-color: #eff6ff;">
|
||||||
|
@if (isCurrentSlotSolved)
|
||||||
|
{
|
||||||
|
<span class="fs-2">@word.Emoji</span>
|
||||||
|
<span class="fw-bold fs-5 text-success">@word.Word</span>
|
||||||
|
<span class="badge bg-success mt-1">Správne!</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="fs-1 text-primary">❓</span>
|
||||||
|
<span class="text-muted fw-bold">Chýba tu!</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="wagon card p-3 rounded-4 shadow-sm @(index < missingTargetIndex ? "bg-light" : "")"
|
||||||
|
style="min-width: 100px; opacity: @(index > missingTargetIndex ? "0.4" : "1");">
|
||||||
|
<span class="fs-3">@word.Emoji</span>
|
||||||
|
<span class="fw-bold fs-5">@word.Word</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Možnosti na výber -->
|
||||||
|
@if (!isCurrentSlotSolved)
|
||||||
|
{
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="fw-bold mb-2">Vyber správne slovo:</p>
|
||||||
|
<div class="d-flex justify-content-center gap-3 flex-wrap">
|
||||||
|
@foreach (var opt in missingOptions)
|
||||||
|
{
|
||||||
|
<button class="btn btn-lg btn-outline-primary rounded-pill px-4 py-2 fw-bold shadow-sm answer-btn"
|
||||||
|
@onclick="() => CheckMissingAnswer(opt)">
|
||||||
|
@opt.Emoji @opt.Word
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-4 animate-bounce">
|
||||||
|
<button class="btn btn-lg btn-success rounded-pill px-5 shadow"
|
||||||
|
@onclick="NextMissingWordRound">
|
||||||
|
Ďalšie slovo ➡️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Phase.AssembleTrain:
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-center bg-white border-0">
|
||||||
|
<h5 class="fw-bold mb-1">Poskladaj celý vláčik od prvého po posledné slovo</h5>
|
||||||
|
<p class="text-muted">Klikaj na slová v správnom poradí, v akom idú vybrané slová za sebou.</p>
|
||||||
|
|
||||||
|
<!-- Zostavený vláčik -->
|
||||||
|
<div class="assembled-track p-3 rounded-4 bg-light mb-4 d-flex flex-wrap align-items-center gap-2" style="min-height: 120px;">
|
||||||
|
<div class="locomotive p-2 px-3 rounded-4 bg-danger text-white fw-bold">
|
||||||
|
<span class="fs-2">🚂</span> Štart
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (assembledWords.Count == 0)
|
||||||
|
{
|
||||||
|
<span class="text-muted fst-italic ms-3">Klikni na 1. slovo v rade...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var w in assembledWords)
|
||||||
|
{
|
||||||
|
<div class="wagon card p-2 px-3 rounded-4 shadow-sm bg-white border-success text-success fw-bold d-flex flex-row align-items-center gap-2">
|
||||||
|
<span class="fs-4">@w.Emoji</span>
|
||||||
|
<span class="fs-5">@w.Word</span>
|
||||||
|
<span class="text-success">✓</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Kocky na výber (zamiešané) -->
|
||||||
|
@if (assembledWords.Count < ActiveWords.Count)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap justify-content-center gap-3">
|
||||||
|
@foreach (var w in shuffledPool)
|
||||||
|
{
|
||||||
|
<button class="btn btn-light border-2 rounded-4 p-3 shadow-sm d-flex flex-column align-items-center transition-transform hover-scale"
|
||||||
|
style="min-width: 100px; border-color: @Category.ThemeColor;"
|
||||||
|
@onclick="() => TryAddAssembledWord(w)">
|
||||||
|
<span class="fs-2">@w.Emoji</span>
|
||||||
|
<span class="fw-bold fs-5 text-dark">@w.Word</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="p-4 bg-success bg-opacity-10 rounded-4 text-center">
|
||||||
|
<h3 class="fw-bold text-success">🎉 Výborne! Zostavil si celý rad!</h3>
|
||||||
|
<p class="text-muted">Vláčik má všetky vagóny v správnom poradí.</p>
|
||||||
|
<button class="btn btn-lg btn-success rounded-pill px-5 shadow mt-2"
|
||||||
|
@onclick="CompleteTrain">
|
||||||
|
Ukončiť a získať hviezdičku ⭐
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LetterCategory Category { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback<int> OnCompleted { get; set; }
|
||||||
|
|
||||||
|
private enum Phase { Learn, MissingWord, AssembleTrain }
|
||||||
|
private Phase currentPhase = Phase.Learn;
|
||||||
|
|
||||||
|
private int selectedChunkGroup = 1;
|
||||||
|
private int MaxChunkGroup => Category.Words.Max(w => w.ChunkGroup);
|
||||||
|
private List<WordItem> ActiveWords = new();
|
||||||
|
|
||||||
|
// Fáza 1
|
||||||
|
private bool isPlayingRhythm = false;
|
||||||
|
private int highlightedIndex = -1;
|
||||||
|
|
||||||
|
// Fáza 2
|
||||||
|
private int missingTargetIndex = 0;
|
||||||
|
private bool isCurrentSlotSolved = false;
|
||||||
|
private List<WordItem> missingOptions = new();
|
||||||
|
|
||||||
|
// Fáza 3
|
||||||
|
private List<WordItem> assembledWords = new();
|
||||||
|
private List<WordItem> shuffledPool = new();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
SelectChunk(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<WordItem> GetGroupWords(int group) =>
|
||||||
|
Category.Words.Where(w => w.ChunkGroup == group).ToList();
|
||||||
|
|
||||||
|
private void SelectChunk(int group)
|
||||||
|
{
|
||||||
|
selectedChunkGroup = group;
|
||||||
|
if (group == -1)
|
||||||
|
ActiveWords = Category.Words.ToList();
|
||||||
|
else
|
||||||
|
ActiveWords = Category.Words.Where(w => w.ChunkGroup == group).ToList();
|
||||||
|
|
||||||
|
ResetPhase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPhase(Phase phase)
|
||||||
|
{
|
||||||
|
currentPhase = phase;
|
||||||
|
ResetPhase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPhase()
|
||||||
|
{
|
||||||
|
isPlayingRhythm = false;
|
||||||
|
highlightedIndex = -1;
|
||||||
|
|
||||||
|
if (currentPhase == Phase.MissingWord)
|
||||||
|
{
|
||||||
|
missingTargetIndex = 0;
|
||||||
|
PrepareMissingWordQuestion();
|
||||||
|
}
|
||||||
|
else if (currentPhase == Phase.AssembleTrain)
|
||||||
|
{
|
||||||
|
assembledWords.Clear();
|
||||||
|
var rnd = new Random();
|
||||||
|
shuffledPool = ActiveWords.OrderBy(_ => rnd.Next()).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fáza 1 metódy
|
||||||
|
private async Task PlaySingleWord(WordItem word)
|
||||||
|
{
|
||||||
|
await Audio.SpeakWordAsync(word.Word);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PlayFullRhythm()
|
||||||
|
{
|
||||||
|
if (isPlayingRhythm) return;
|
||||||
|
isPlayingRhythm = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < ActiveWords.Count; i++)
|
||||||
|
{
|
||||||
|
highlightedIndex = i;
|
||||||
|
StateHasChanged();
|
||||||
|
await Audio.SpeakWordAsync(ActiveWords[i].Word);
|
||||||
|
await Task.Delay(1650);
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightedIndex = -1;
|
||||||
|
isPlayingRhythm = false;
|
||||||
|
StateHasChanged();
|
||||||
|
await Audio.PlaySuccessAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fáza 2 metódy
|
||||||
|
private void PrepareMissingWordQuestion()
|
||||||
|
{
|
||||||
|
isCurrentSlotSolved = false;
|
||||||
|
var targetWord = ActiveWords[missingTargetIndex];
|
||||||
|
|
||||||
|
var rnd = new Random();
|
||||||
|
var distractors = Category.Words
|
||||||
|
.Where(w => w.Word != targetWord.Word)
|
||||||
|
.OrderBy(_ => rnd.Next())
|
||||||
|
.Take(2)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
distractors.Add(targetWord);
|
||||||
|
missingOptions = distractors.OrderBy(_ => rnd.Next()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckMissingAnswer(WordItem selected)
|
||||||
|
{
|
||||||
|
var target = ActiveWords[missingTargetIndex];
|
||||||
|
if (selected.Word == target.Word)
|
||||||
|
{
|
||||||
|
isCurrentSlotSolved = true;
|
||||||
|
await Audio.PlaySuccessAsync();
|
||||||
|
await Audio.SpeakWordAsync(selected.Word);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Audio.PlayWrongAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextMissingWordRound()
|
||||||
|
{
|
||||||
|
missingTargetIndex++;
|
||||||
|
if (missingTargetIndex >= ActiveWords.Count)
|
||||||
|
{
|
||||||
|
// Dokončil celú sériu chýbajúcich slov
|
||||||
|
currentPhase = Phase.AssembleTrain;
|
||||||
|
ResetPhase();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrepareMissingWordQuestion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fáza 3 metódy
|
||||||
|
private async Task TryAddAssembledWord(WordItem word)
|
||||||
|
{
|
||||||
|
int nextExpectedIndex = assembledWords.Count;
|
||||||
|
if (nextExpectedIndex < ActiveWords.Count && ActiveWords[nextExpectedIndex].Word == word.Word)
|
||||||
|
{
|
||||||
|
assembledWords.Add(word);
|
||||||
|
shuffledPool.Remove(word);
|
||||||
|
await Audio.SpeakWordAsync(word.Word);
|
||||||
|
|
||||||
|
if (assembledWords.Count == ActiveWords.Count)
|
||||||
|
{
|
||||||
|
await Audio.PlayFanfareAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Audio.PlayWrongAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CompleteTrain()
|
||||||
|
{
|
||||||
|
await Audio.PlayFanfareAsync();
|
||||||
|
await OnCompleted.InvokeAsync(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
<div class="voice-settings-bar d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<!-- Prepínač Sprievodcov: Víla Ypsilka / Škriatok Ypsík -->
|
||||||
|
<div class="btn-group btn-group-sm shadow-sm rounded-pill p-1 bg-light border" role="group">
|
||||||
|
<button type="button"
|
||||||
|
class="btn rounded-pill px-3 py-1 fw-bold @(currentGender == "female" && string.IsNullOrEmpty(selectedVoiceURI) ? "btn-primary shadow-sm" : "btn-light text-secondary")"
|
||||||
|
@onclick="@(() => ChangeGender("female"))"
|
||||||
|
title="Sprievodkyňa: Víla Ypsilka (čistý ženský hlas)">
|
||||||
|
🧚♀️ Víla Ypsilka
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn rounded-pill px-3 py-1 fw-bold @(currentGender == "male" && string.IsNullOrEmpty(selectedVoiceURI) ? "btn-primary shadow-sm" : "btn-light text-secondary")"
|
||||||
|
@onclick="@(() => ChangeGender("male"))"
|
||||||
|
title="Sprievodca: Škriatok Ypsík (mužský hlas)">
|
||||||
|
🧙♂️ Škriatok Ypsík
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Výber hudby na pozadí a zapnutie/vypnutie (6 pestrých pesničiek) -->
|
||||||
|
<div class="d-flex align-items-center gap-1 bg-white p-1 rounded-pill border shadow-sm">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm rounded-pill px-3 py-1 fw-bold @(isMusicPlaying ? "btn-warning text-dark" : "btn-light text-secondary border-0")"
|
||||||
|
@onclick="ToggleMusic"
|
||||||
|
title="@(isMusicPlaying ? "Stíšiť / Pozastaviť hudbu" : "Spustiť hudbu")">
|
||||||
|
<span>@(isMusicPlaying ? "🎶" : "🎵")</span>
|
||||||
|
<span class="ms-1 d-none d-sm-inline">@(isMusicPlaying ? "Hrá" : "Hudba")</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Spoľahlivý výber piesne (Combo Box) -->
|
||||||
|
<select class="form-select form-select-sm border-0 bg-transparent fw-medium pe-4 cursor-pointer"
|
||||||
|
style="max-width: 220px; font-size: 0.85rem; box-shadow: none;"
|
||||||
|
value="@currentTrackIdx"
|
||||||
|
@onchange="OnTrackSelected">
|
||||||
|
@if (tracks.Count == 0)
|
||||||
|
{
|
||||||
|
<option value="0">🍃 Pokojné Ypsilonkovo (Chill)</option>
|
||||||
|
<option value="1">🧚♀️ Les víly Ypsilky</option>
|
||||||
|
<option value="2">🎪 Hravé námestie</option>
|
||||||
|
<option value="3">⭐ Hviezdna uspávanka</option>
|
||||||
|
<option value="4">🏰 Kráľovský hrad</option>
|
||||||
|
<option value="5">🛶 Slnečná riečka</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var t in tracks)
|
||||||
|
{
|
||||||
|
<option value="@t.Index">@t.Name</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Výber rýchlosti -->
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-light border rounded-pill px-3 py-1 fw-medium dropdown-toggle shadow-sm"
|
||||||
|
type="button" id="speedDropdown"
|
||||||
|
@onclick="ToggleSpeedMenu">
|
||||||
|
@(currentSpeed <= 0.70 ? "🐢 Pomalšie" : (currentSpeed <= 0.80 ? "🦔 Príjemné" : "🐇 Štandardné"))
|
||||||
|
</button>
|
||||||
|
@if (showSpeedMenu)
|
||||||
|
{
|
||||||
|
<ul class="dropdown-menu show shadow-lg border-0 rounded-4 p-2 mt-1" style="position: absolute; z-index: 1050;">
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item rounded-pill px-3 py-2 fw-medium @(currentSpeed <= 0.70 ? "active" : "")"
|
||||||
|
@onclick="@(() => ChangeSpeed(0.68))">
|
||||||
|
🐢 Veľmi pomalé (0.68x) – pre začiatok
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item rounded-pill px-3 py-2 fw-medium @(currentSpeed > 0.70 && currentSpeed <= 0.80 ? "active" : "")"
|
||||||
|
@onclick="@(() => ChangeSpeed(0.75))">
|
||||||
|
🦔 Príjemné a zreteľné (0.75x) – odporúčané
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item rounded-pill px-3 py-2 fw-medium @(currentSpeed > 0.80 ? "active" : "")"
|
||||||
|
@onclick="@(() => ChangeSpeed(0.88))">
|
||||||
|
🐇 Štandardné tempo (0.88x)
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tlačidlo Nastavenia hlasov & Diagnostika -->
|
||||||
|
<button class="btn btn-sm btn-outline-secondary rounded-pill px-3 py-1 shadow-sm d-flex align-items-center gap-1"
|
||||||
|
@onclick="OpenVoiceModal"
|
||||||
|
title="Podrobné nastavenia a výber hlasov zo systému">
|
||||||
|
<span>⚙️</span>
|
||||||
|
<span class="d-none d-md-inline">Hlasy</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Test hlasu -->
|
||||||
|
<button class="btn btn-sm btn-outline-primary rounded-pill px-3 py-1 shadow-sm d-flex align-items-center gap-1"
|
||||||
|
@onclick="TestVoice"
|
||||||
|
title="Pozdrav od vybraného sprievodcu">
|
||||||
|
<span>🔊</span>
|
||||||
|
<span class="d-none d-sm-inline">Pozdrav</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODÁLNE OKNO: VŠETKY HLASY SYSTÉMU -->
|
||||||
|
@if (showVoiceModal)
|
||||||
|
{
|
||||||
|
<div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5); z-index: 99999;">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||||
|
<div class="modal-content rounded-4 border-0 shadow-lg">
|
||||||
|
<div class="modal-header border-0 text-white rounded-top-4" style="background: linear-gradient(135deg, #312e81, #4f46e5);">
|
||||||
|
<h5 class="modal-title fw-bold">🎙️ Výber hlasu a reči v prehliadači</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" @onclick="() => showVoiceModal = false"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4 bg-light">
|
||||||
|
<!-- Vysvetlenie ako to funguje -->
|
||||||
|
<div class="alert alert-info rounded-4 border-0 shadow-sm mb-4">
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<span class="fs-2">💡</span>
|
||||||
|
<div>
|
||||||
|
<h6 class="fw-bold mb-1">Ako fungujú hlasy v prehliadači?</h6>
|
||||||
|
<p class="small m-0 text-secondary">
|
||||||
|
Prehliadač využíva hlasy nainštalované vo vašom systéme Windows a prehliadači (napr. Microsoft Edge ponúka najkvalitnejšie neurálne Natural hlasy).
|
||||||
|
Ak máte v systéme iba jeden slovenský hlas, ženský a mužský hlas znie rovnako, preto môžete zvoliť aj príjemný český hlas (napr. <em>Vlasta</em>) alebo si doinštalovať ďalší hlas vo Windows.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6 class="fw-bold text-primary mb-2">🇸🇰 Slovenské a 🇨🇿 české hlasy nájdené vo vašom zariadení:</h6>
|
||||||
|
|
||||||
|
@if (availableVoices.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="p-4 bg-white rounded-4 text-center text-muted">
|
||||||
|
<p class="m-0">Hľadáme hlasy v prehliadači...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var relevantVoices = availableVoices.Where(v => v.IsSlovak || v.IsCzech).ToList();
|
||||||
|
if (relevantVoices.Count == 0)
|
||||||
|
{
|
||||||
|
relevantVoices = availableVoices.Take(6).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="list-group rounded-4 shadow-sm mb-4 border-0 overflow-hidden">
|
||||||
|
@foreach (var v in relevantVoices)
|
||||||
|
{
|
||||||
|
var isSelected = selectedVoiceURI == v.Uri || (string.IsNullOrEmpty(selectedVoiceURI) && ((currentGender == "female" && v.IsFemale) || (currentGender == "male" && v.IsMale)));
|
||||||
|
<div class="list-group-item list-group-item-action p-3 d-flex justify-content-between align-items-center @(isSelected ? "bg-primary bg-opacity-10 border-primary" : "")">
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="fs-4">@(v.IsFemale ? "👩" : "👨")</span>
|
||||||
|
<strong class="text-dark">@v.Name</strong>
|
||||||
|
@if (v.IsNatural)
|
||||||
|
{
|
||||||
|
<span class="badge bg-success rounded-pill">Natural (prirodzený)</span>
|
||||||
|
}
|
||||||
|
<span class="badge bg-secondary rounded-pill">@v.Lang</span>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">
|
||||||
|
@(v.IsSlovak ? "🇸🇰 Slovenský jazyk" : (v.IsCzech ? "🇨🇿 Český jazyk (veľmi blízky)" : v.Lang))
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
<button class="btn btn-sm btn-outline-primary rounded-pill px-3"
|
||||||
|
@onclick="() => SelectSpecificVoice(v.Uri)">
|
||||||
|
@(isSelected ? "✓ Zvolený" : "Zvoliť")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Návod pre Windows -->
|
||||||
|
<div class="card p-3 rounded-4 bg-white border-0 shadow-sm">
|
||||||
|
<h6 class="fw-bold m-0 text-dark">Tip: Ako získať viac špičkových slovenských hlasov?</h6>
|
||||||
|
<ol class="small text-muted mt-2 mb-0 ps-3">
|
||||||
|
<li>Otvorte hru v prehliadači <strong>Microsoft Edge</strong> – ten má automaticky zabudovaný špičkový neurálny ženský hlas <em>Viktoria Online (Natural)</em> aj mužský hlas <em>Filip Online (Natural)</em>.</li>
|
||||||
|
<li>Vo Windows: Otvorte <em>Nastavenia → Čas a jazyk → Reč → Pridať hlasy</em> a vyhľadajte <em>Slovenčina</em>.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0 bg-light rounded-bottom-4">
|
||||||
|
<button type="button" class="btn btn-primary rounded-pill px-4" @onclick="CloseVoiceModal">
|
||||||
|
Hotovo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string currentGender = "female";
|
||||||
|
private double currentSpeed = 0.75;
|
||||||
|
private string selectedVoiceURI = string.Empty;
|
||||||
|
private bool showSpeedMenu = false;
|
||||||
|
private bool showVoiceModal = false;
|
||||||
|
private bool isMusicPlaying = false;
|
||||||
|
private int currentTrackIdx = 0;
|
||||||
|
private List<TrackInfo> tracks = new();
|
||||||
|
private List<VoiceItemInfo> availableVoices = new();
|
||||||
|
|
||||||
|
private DotNetObjectReference<VoiceSettingsBar>? dotNetHelper;
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
dotNetHelper = DotNetObjectReference.Create(this);
|
||||||
|
await JS.InvokeVoidAsync("gameAudio.registerVoiceSettingsHelper", dotNetHelper);
|
||||||
|
|
||||||
|
var settings = await Audio.GetVoiceSettingsAsync();
|
||||||
|
if (!string.IsNullOrEmpty(settings.Gender))
|
||||||
|
{
|
||||||
|
currentGender = settings.Gender;
|
||||||
|
}
|
||||||
|
if (settings.Speed > 0)
|
||||||
|
{
|
||||||
|
currentSpeed = settings.Speed;
|
||||||
|
}
|
||||||
|
selectedVoiceURI = settings.SelectedVoiceURI ?? string.Empty;
|
||||||
|
isMusicPlaying = await Audio.IsBackgroundMusicPlayingAsync();
|
||||||
|
tracks = await Audio.GetTrackListAsync();
|
||||||
|
currentTrackIdx = await Audio.GetCurrentTrackIndexAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public void OnTrackChangedExternal(int trackIndex, bool isPlaying)
|
||||||
|
{
|
||||||
|
currentTrackIdx = trackIndex;
|
||||||
|
isMusicPlaying = isPlaying;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleMusic()
|
||||||
|
{
|
||||||
|
isMusicPlaying = await Audio.ToggleBackgroundMusicAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnTrackSelected(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Value != null && int.TryParse(e.Value.ToString(), out var idx))
|
||||||
|
{
|
||||||
|
currentTrackIdx = idx;
|
||||||
|
await Audio.SetTrackAsync(idx);
|
||||||
|
isMusicPlaying = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectTrack(int index)
|
||||||
|
{
|
||||||
|
currentTrackIdx = index;
|
||||||
|
await Audio.SetTrackAsync(index);
|
||||||
|
isMusicPlaying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ChangeGender(string gender)
|
||||||
|
{
|
||||||
|
currentGender = gender;
|
||||||
|
selectedVoiceURI = string.Empty;
|
||||||
|
await Audio.SetVoiceGenderAsync(gender);
|
||||||
|
await Audio.TestVoiceSpeechAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ChangeSpeed(double speed)
|
||||||
|
{
|
||||||
|
currentSpeed = speed;
|
||||||
|
showSpeedMenu = false;
|
||||||
|
await Audio.SetVoiceSpeedAsync(speed);
|
||||||
|
await Audio.TestVoiceSpeechAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSpeedMenu()
|
||||||
|
{
|
||||||
|
showSpeedMenu = !showSpeedMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenVoiceModal()
|
||||||
|
{
|
||||||
|
availableVoices = await Audio.GetAvailableVoicesAsync();
|
||||||
|
showVoiceModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseVoiceModal()
|
||||||
|
{
|
||||||
|
showVoiceModal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectSpecificVoice(string uri)
|
||||||
|
{
|
||||||
|
selectedVoiceURI = uri;
|
||||||
|
await Audio.SetSelectedVoiceURIAsync(uri);
|
||||||
|
await Audio.TestVoiceSpeechAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TestVoice()
|
||||||
|
{
|
||||||
|
await Audio.TestVoiceSpeechAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
dotNetHelper?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="meaning-quiz-container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 bg-white p-3 rounded-4 shadow-sm">
|
||||||
|
<div>
|
||||||
|
<h4 class="m-0 fw-bold text-success">📖 Hádanky mudrca: Kto som? (@Category.Letter)</h4>
|
||||||
|
<small class="text-muted">Spoznaj skutočný význam vybraných slov a archaizmov</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<span class="badge bg-success fs-6 px-3 py-2 rounded-pill">
|
||||||
|
Správne: @correctCount / @totalQuestions
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-light text-dark fs-6 px-3 py-2 rounded-pill border">
|
||||||
|
Otázka @(currentIndex + 1) z @totalQuestions
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!isQuizFinished)
|
||||||
|
{
|
||||||
|
var current = quizItems[currentIndex];
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm bg-white border-0 text-center">
|
||||||
|
<div class="mb-4">
|
||||||
|
<span class="badge bg-warning text-dark px-3 py-1 rounded-pill mb-2">Hádanka</span>
|
||||||
|
<h3 class="fw-bold text-dark mb-3">„@current.Meaning“</h3>
|
||||||
|
<p class="text-muted">Ktoré vybrané slovo skrýva tento význam?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Možnosti slov -->
|
||||||
|
<div class="row g-3 justify-content-center">
|
||||||
|
@foreach (var opt in currentOptions)
|
||||||
|
{
|
||||||
|
var isSelected = selectedWord == opt.Word;
|
||||||
|
var isCorrect = opt.Word == current.Word;
|
||||||
|
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<button class="btn btn-lg w-100 p-4 rounded-4 shadow-sm text-center transition-transform hover-scale
|
||||||
|
@(selectedWord != null ? (isCorrect ? "btn-success" : (isSelected ? "btn-danger" : "btn-light")) : "btn-light border-2")"
|
||||||
|
style="font-size: 1.35rem; font-weight: 700;"
|
||||||
|
disabled="@(selectedWord != null)"
|
||||||
|
@onclick="() => SelectOption(opt)">
|
||||||
|
@if (selectedWord != null && isCorrect)
|
||||||
|
{
|
||||||
|
<span class="fs-1 d-block mb-1">@opt.Emoji</span>
|
||||||
|
}
|
||||||
|
<div>@opt.Word</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vysvetlenie a príkladová veta po odpovedi -->
|
||||||
|
@if (selectedWord != null)
|
||||||
|
{
|
||||||
|
<div class="mt-4 p-4 rounded-4 bg-light text-start shadow-sm border animate-fade-in">
|
||||||
|
<div class="d-flex align-items-center gap-3 mb-2">
|
||||||
|
<span class="fs-1">@current.Emoji</span>
|
||||||
|
<div>
|
||||||
|
<h4 class="fw-bold m-0 @(selectedWord == current.Word ? "text-success" : "text-primary")">
|
||||||
|
@current.Word
|
||||||
|
</h4>
|
||||||
|
<span class="text-muted">@current.Meaning</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<p class="fs-5 m-0 text-secondary">
|
||||||
|
<strong>Príklad vo vete:</strong> „@current.ExampleSentence“
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="text-end mt-3">
|
||||||
|
<button class="btn btn-lg btn-success rounded-pill px-5 shadow"
|
||||||
|
@onclick="NextQuestion">
|
||||||
|
@(currentIndex + 1 >= totalQuestions ? "Dokončiť kvíz 🏁" : "Ďalšia hádanka ➡️")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Výsledky kvízu -->
|
||||||
|
<div class="card p-5 rounded-4 shadow text-center bg-white border-0">
|
||||||
|
<span class="fs-1">📜</span>
|
||||||
|
<h2 class="fw-bold mt-2 text-success">Kvíz mudrca dokončený!</h2>
|
||||||
|
<p class="fs-5 text-muted">Uhádol si <strong>@correctCount</strong> z @totalQuestions významov slov.</p>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center my-3">
|
||||||
|
<div class="p-3 bg-success bg-opacity-10 text-success rounded-4">
|
||||||
|
<h4>⭐ Získavaš Odznak Múdrosti provincie @Category.Letter! ⭐</h4>
|
||||||
|
<p class="m-0">Teraz už presne vieš, čo jednotlivé vybrané slová znamenajú v bežnom živote.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center gap-3 mt-4">
|
||||||
|
<button class="btn btn-outline-success btn-lg rounded-pill px-4" @onclick="RestartQuiz">
|
||||||
|
🔄 Hrať znova
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success btn-lg rounded-pill px-5 shadow" @onclick="FinishQuiz">
|
||||||
|
Vrátiť sa k provincii 🗺️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public LetterCategory Category { get; set; } = new();
|
||||||
|
[Parameter] public EventCallback<int> OnCompleted { get; set; }
|
||||||
|
|
||||||
|
private List<WordItem> quizItems = new();
|
||||||
|
private List<WordItem> currentOptions = new();
|
||||||
|
private int currentIndex = 0;
|
||||||
|
private const int totalQuestions = 5;
|
||||||
|
private int correctCount = 0;
|
||||||
|
private bool isQuizFinished = false;
|
||||||
|
private string? selectedWord = null;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
RestartQuiz();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartQuiz()
|
||||||
|
{
|
||||||
|
currentIndex = 0;
|
||||||
|
correctCount = 0;
|
||||||
|
isQuizFinished = false;
|
||||||
|
selectedWord = null;
|
||||||
|
|
||||||
|
var rnd = new Random();
|
||||||
|
quizItems = Category.Words.OrderBy(_ => rnd.Next()).Take(totalQuestions).ToList();
|
||||||
|
PrepareQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareQuestion()
|
||||||
|
{
|
||||||
|
selectedWord = null;
|
||||||
|
var current = quizItems[currentIndex];
|
||||||
|
var rnd = new Random();
|
||||||
|
|
||||||
|
var distractors = Category.Words
|
||||||
|
.Where(w => w.Word != current.Word)
|
||||||
|
.OrderBy(_ => rnd.Next())
|
||||||
|
.Take(2)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
distractors.Add(current);
|
||||||
|
currentOptions = distractors.OrderBy(_ => rnd.Next()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectOption(WordItem opt)
|
||||||
|
{
|
||||||
|
selectedWord = opt.Word;
|
||||||
|
var current = quizItems[currentIndex];
|
||||||
|
|
||||||
|
if (opt.Word == current.Word)
|
||||||
|
{
|
||||||
|
correctCount++;
|
||||||
|
await Audio.PlaySuccessAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Audio.PlayWrongAsync();
|
||||||
|
}
|
||||||
|
await Audio.SpeakWordAsync(current.Word);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextQuestion()
|
||||||
|
{
|
||||||
|
currentIndex++;
|
||||||
|
if (currentIndex >= totalQuestions)
|
||||||
|
{
|
||||||
|
isQuizFinished = true;
|
||||||
|
_ = Audio.PlayFanfareAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrepareQuestion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FinishQuiz()
|
||||||
|
{
|
||||||
|
await OnCompleted.InvokeAsync(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="sidebar">
|
||||||
|
<NavMenu />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="top-row px-3 px-md-4 bg-white shadow-sm d-flex flex-wrap justify-content-between align-items-center gap-2 py-2" style="min-height: 4rem;">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="fs-4">✨</span>
|
||||||
|
<span class="fw-bold text-primary fs-5">Ypsilonkovo</span>
|
||||||
|
<span class="badge bg-light text-secondary rounded-pill border d-none d-lg-inline">Slovenské vybrané slová</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nastavenia hlasu a rýchlosti -->
|
||||||
|
<VoiceSettingsBar />
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<a href="lexicon" class="btn btn-sm btn-outline-primary rounded-pill px-3 shadow-sm">
|
||||||
|
📖 Knižnica slov
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="content px-3 px-md-4 py-4" style="background-color: #f8fafc; min-height: calc(100vh - 3.5rem);">
|
||||||
|
@Body
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border-bottom: 1px solid #d6d5d5;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 3.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a:first-child {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640.98px) {
|
||||||
|
.top-row {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.page {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 250px;
|
||||||
|
height: 100vh;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row.auth ::deep a:first-child {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row, article {
|
||||||
|
padding-left: 2rem !important;
|
||||||
|
padding-right: 1.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
@inject WordRepository Repository
|
||||||
|
|
||||||
|
<div class="top-row ps-3 navbar navbar-dark bg-indigo">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand fw-bold d-flex align-items-center gap-2" href="">
|
||||||
|
<img src="images/logo.png" alt="Logo" class="rounded-3 shadow-sm" style="width: 36px; height: 36px; object-fit: cover; border: 1.5px solid rgba(255,255,255,0.4);" />
|
||||||
|
<span>Ypsilonkovo</span>
|
||||||
|
</a>
|
||||||
|
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||||
|
<nav class="nav flex-column py-2">
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link rounded-pill mb-1" href="" Match="NavLinkMatch.All">
|
||||||
|
<span class="me-2">🗺️</span> Mapa kráľovstva
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link rounded-pill mb-2" href="lexicon">
|
||||||
|
<span class="me-2">📖</span> Kniha slov
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-3 pt-2 pb-1 text-white text-opacity-75 small text-uppercase fw-bold">
|
||||||
|
Provincie písmen:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (var cat in Repository.GetAllCategories())
|
||||||
|
{
|
||||||
|
var path = $"province/{cat.Letter}";
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link rounded-pill mb-1 small" href="@path">
|
||||||
|
<span class="me-2">@cat.Icon</span> @cat.Title
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool collapseNavMenu = true;
|
||||||
|
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||||
|
|
||||||
|
private void ToggleNavMenu()
|
||||||
|
{
|
||||||
|
collapseNavMenu = !collapseNavMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
.navbar-toggler {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
min-height: 3.5rem;
|
||||||
|
background: linear-gradient(135deg, #312e81, #1e1b4b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #facc15 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep a {
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-radius: 9999px;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep a.active {
|
||||||
|
background: linear-gradient(135deg, #4f46e5, #6366f1);
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep a:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.navbar-toggler {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-scrollable {
|
||||||
|
height: calc(100vh - 3.5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #1e1b4b;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
namespace Ypsilonkovo.Models;
|
||||||
|
|
||||||
|
public class WordItem
|
||||||
|
{
|
||||||
|
public string Word { get; set; } = string.Empty;
|
||||||
|
public string Letter { get; set; } = string.Empty; // B, M, P, R, S, V, Z
|
||||||
|
public int ChunkGroup { get; set; } = 1; // 1, 2, 3... for chunking/progressive learning
|
||||||
|
public string Meaning { get; set; } = string.Empty; // Detské vysvetlenie
|
||||||
|
public string ExampleSentence { get; set; } = string.Empty; // Ukážková veta
|
||||||
|
public string Emoji { get; set; } = "✨"; // Vizuálny symbol
|
||||||
|
public bool IsArchaicOrDifficult { get; set; } = false; // Označenie zložitejších slov (byľ, vyžla, poryv...)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlienItem
|
||||||
|
{
|
||||||
|
public string Word { get; set; } = string.Empty;
|
||||||
|
public bool IsAlien { get; set; } // True if with soft 'i' or wrong letter
|
||||||
|
public string Reason { get; set; } = string.Empty; // Prečo je to votrelec
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LetterCategory
|
||||||
|
{
|
||||||
|
public string Letter { get; set; } = string.Empty; // "B", "M", "P", "R", "S", "V", "Z"
|
||||||
|
public string Title { get; set; } = string.Empty; // "Borovicový les (B)"
|
||||||
|
public string Subtitle { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public string Icon { get; set; } = "🌲";
|
||||||
|
public string ThemeColor { get; set; } = "#22c55e"; // CSS color
|
||||||
|
public string BgGradient { get; set; } = "linear-gradient(135deg, #15803d, #22c55e)";
|
||||||
|
public int RequiredStarsToUnlock { get; set; } = 0;
|
||||||
|
public List<WordItem> Words { get; set; } = new();
|
||||||
|
public List<AlienItem> AlienPool { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlayerProgress
|
||||||
|
{
|
||||||
|
public int TotalStars { get; set; } = 0;
|
||||||
|
public HashSet<string> UnlockedLetters { get; set; } = new() { "B" }; // B is always unlocked at start
|
||||||
|
public Dictionary<string, int> LetterStars { get; set; } = new(); // Letter -> stars (0-3)
|
||||||
|
public HashSet<string> DefeatedBosses { get; set; } = new(); // Letters with boss defeated
|
||||||
|
public HashSet<string> CompletedTrains { get; set; } = new();
|
||||||
|
public HashSet<string> CompletedAliens { get; set; } = new();
|
||||||
|
public HashSet<string> CompletedQuizzes { get; set; } = new();
|
||||||
|
|
||||||
|
public int GetStarsForLetter(string letter) => LetterStars.TryGetValue(letter, out var s) ? s : 0;
|
||||||
|
|
||||||
|
public void AddStarsForLetter(string letter, int stars)
|
||||||
|
{
|
||||||
|
if (!LetterStars.ContainsKey(letter) || LetterStars[letter] < stars)
|
||||||
|
{
|
||||||
|
LetterStars[letter] = stars;
|
||||||
|
}
|
||||||
|
TotalStars = LetterStars.Values.Sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
@page "/"
|
||||||
|
@inject WordRepository Repository
|
||||||
|
@inject StorageService Storage
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
<div class="home-container">
|
||||||
|
<!-- Príbehový banner kráľovstva -->
|
||||||
|
<div class="card p-4 p-md-5 rounded-4 text-white shadow-lg mb-4 border-0 position-relative overflow-hidden"
|
||||||
|
style="background: linear-gradient(135deg, #1e1b4b 0%, #312e81 40%, #4338ca 100%);">
|
||||||
|
<div class="position-relative z-1">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-4">
|
||||||
|
<div class="d-flex align-items-center gap-4">
|
||||||
|
<img src="images/logo.png" alt="Ypsilonkovo Hrad" class="rounded-4 shadow-lg d-none d-sm-block hover-scale transition-all"
|
||||||
|
style="width: 110px; height: 110px; object-fit: cover; border: 3px solid rgba(255,255,255,0.3);" />
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-warning text-dark px-3 py-2 rounded-pill fw-bold mb-2">
|
||||||
|
✨ Kráľovstvo Ypsilonkovo
|
||||||
|
</span>
|
||||||
|
<h1 class="display-5 fw-extrabold m-0">Záchrana vybraných slov</h1>
|
||||||
|
<p class="fs-5 mt-2 opacity-90 mb-0" style="max-width: 600px;">
|
||||||
|
Zákerný škriatok <strong>Mäkkýš</strong> rozhádzal pravopis a pomiešal vybrané slová s mäkkým I!
|
||||||
|
Tvoji sprievodcovia <strong>🧚♀️ Víla Ypsilka</strong> a <strong>🧙♂️ Škriatok Ypsík</strong> ťa prevedú provinciami, pomôžu ti opraviť mosty a stať sa <strong>Veľkým Strážcom Ypsilonu</strong>!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Štatistický panel hráča -->
|
||||||
|
<div class="bg-white bg-opacity-10 p-3 p-md-4 rounded-4 backdrop-blur shadow-sm border border-white border-opacity-20 text-center">
|
||||||
|
<div class="d-flex gap-4 justify-content-center align-items-center">
|
||||||
|
<div>
|
||||||
|
<div class="fs-1">⭐</div>
|
||||||
|
<div class="display-6 fw-bold">@progress.TotalStars</div>
|
||||||
|
<small class="text-uppercase tracking-wider opacity-75">Hviezd</small>
|
||||||
|
</div>
|
||||||
|
<div class="vr bg-white opacity-25"></div>
|
||||||
|
<div>
|
||||||
|
<div class="fs-1">🎖️</div>
|
||||||
|
<div class="display-6 fw-bold">@progress.DefeatedBosses.Count / 7</div>
|
||||||
|
<small class="text-uppercase tracking-wider opacity-75">Pečatí</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex flex-column gap-2">
|
||||||
|
<a href="/lexicon" class="btn btn-warning text-dark fw-bold rounded-pill px-4 shadow-sm w-100">
|
||||||
|
📖 Kúzelná knižnica slov
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-outline-light rounded-pill px-3 py-1 small fw-medium" @onclick="PlayAnthem">
|
||||||
|
👑 Pustiť hymnu Ypsilonkova 🎶
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mapa provincií -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="fw-bold m-0 text-dark">🗺️ Mapa kráľovstva Ypsilonkovo</h3>
|
||||||
|
<p class="text-muted m-0">Vyber si provinciu a začni memorovať a hrať sa:</p>
|
||||||
|
</div>
|
||||||
|
@if (progress.TotalStars > 0)
|
||||||
|
{
|
||||||
|
<button class="btn btn-outline-danger btn-sm rounded-pill px-3" @onclick="ResetProgressConfirm">
|
||||||
|
🗑️ Nová hra od začiatku
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
@foreach (var cat in Repository.GetAllCategories())
|
||||||
|
{
|
||||||
|
var isUnlocked = progress.UnlockedLetters.Contains(cat.Letter) || progress.TotalStars >= cat.RequiredStarsToUnlock;
|
||||||
|
var stars = progress.GetStarsForLetter(cat.Letter);
|
||||||
|
var isBossDefeated = progress.DefeatedBosses.Contains(cat.Letter);
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card h-100 rounded-4 border-0 shadow-sm transition-all @(isUnlocked ? "hover-scale cursor-pointer" : "opacity-75 locked-card")"
|
||||||
|
style="background-color: white; border-top: 6px solid @cat.ThemeColor !important;"
|
||||||
|
@onclick="() => SelectProvince(cat, isUnlocked)">
|
||||||
|
<div class="card-body p-4 d-flex flex-column justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||||
|
<span class="fs-1">@cat.Icon</span>
|
||||||
|
<div>
|
||||||
|
@if (isBossDefeated)
|
||||||
|
{
|
||||||
|
<span class="badge bg-warning text-dark px-2 py-1 rounded-pill shadow-sm" title="Zlatá pečať získaná!">
|
||||||
|
🎖️ Pečať
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (!isUnlocked)
|
||||||
|
{
|
||||||
|
<span class="badge bg-secondary px-2 py-1 rounded-pill">
|
||||||
|
🔒 Potrebných @cat.RequiredStarsToUnlock ⭐
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="fw-bold text-dark m-0">@cat.Title</h4>
|
||||||
|
<p class="text-muted small mt-1 mb-2">@cat.Subtitle</p>
|
||||||
|
<p class="small text-secondary">@cat.Description</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 pt-3 border-top d-flex justify-content-between align-items-center">
|
||||||
|
<!-- Hviezdičky -->
|
||||||
|
<div class="d-flex gap-1 fs-5">
|
||||||
|
@for (int s = 1; s <= 3; s++)
|
||||||
|
{
|
||||||
|
<span class="@(s <= stars ? "text-warning" : "text-black-50")">
|
||||||
|
@(s <= stars ? "⭐" : "☆")
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@if (isUnlocked)
|
||||||
|
{
|
||||||
|
<button class="btn btn-sm btn-primary rounded-pill px-3 fw-bold shadow-sm">
|
||||||
|
Vstúpiť ➡️
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted small fw-bold">Zamknuté 🔒</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ako hra pomáha deťom (Sprievodca pre rodičov a učiteľov) -->
|
||||||
|
<div class="card p-4 rounded-4 bg-white border-0 shadow-sm mt-5">
|
||||||
|
<h4 class="fw-bold text-primary mb-3">🧠 Ako Ypsilonkovo učí deti vybrané slová naspamäť?</h4>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="p-3 bg-light rounded-4 h-100">
|
||||||
|
<h5 class="fw-bold">1. Dávkovanie & Rytmus 🎵</h5>
|
||||||
|
<p class="text-muted small m-0">
|
||||||
|
Namiesto bifľovania celého radu naraz sa slová učia v miniskupinách (chunking po 3-5 slovách) sprevádzané rytmickým zvončekom a hlasovým prečítaním.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="p-3 bg-light rounded-4 h-100">
|
||||||
|
<h5 class="fw-bold">2. Lov na votrelcov 👾</h5>
|
||||||
|
<p class="text-muted small m-0">
|
||||||
|
Deti okamžite rozoznávajú slová s mäkkým I (*bič, miska, pivo, sirup*), ktoré do vybraných slov nepatria, čím sa buduje silný pravopisný reflex.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="p-3 bg-light rounded-4 h-100">
|
||||||
|
<h5 class="fw-bold">3. Pochopenie archaizmov 💡</h5>
|
||||||
|
<p class="text-muted small m-0">
|
||||||
|
Deti často zlyhávajú, pretože nepoznajú význam slov ako *byľ, vyžla, poryv, pomyje*. Vizuálny lexikón a hádanky im významy prirodzene vysvetlia.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private PlayerProgress progress = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
progress = await Storage.LoadProgressAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectProvince(LetterCategory cat, bool isUnlocked)
|
||||||
|
{
|
||||||
|
if (isUnlocked)
|
||||||
|
{
|
||||||
|
Nav.NavigateTo($"/province/{cat.Letter}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ResetProgressConfirm()
|
||||||
|
{
|
||||||
|
progress = new PlayerProgress();
|
||||||
|
await Storage.SaveProgressAsync(progress);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PlayAnthem()
|
||||||
|
{
|
||||||
|
await Audio.SetTrackAsync(0);
|
||||||
|
await Audio.LaunchConfettiAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
@page "/lexicon"
|
||||||
|
@inject WordRepository Repository
|
||||||
|
@inject AudioService Audio
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
|
<div class="lexicon-page">
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-white mb-4 border-0"
|
||||||
|
style="background: linear-gradient(135deg, #1e3a8a, #3b82f6);">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<button class="btn btn-light rounded-circle shadow p-2 px-3 fw-bold fs-5" @onclick="GoHome" title="Späť na mapu">
|
||||||
|
⬅️
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<h2 class="fw-bold m-0">📖 Kúzelná kniha vybraných slov</h2>
|
||||||
|
<p class="m-0 mt-1 opacity-90">Prehľad všetkých vybraných slov kráľovstva Ypsilonkovo s významami a slovenským hlasom</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-warning text-dark fs-6 px-3 py-2 rounded-pill">
|
||||||
|
Celkovo: @filteredWords.Count slov
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filtre a vyhľadávanie -->
|
||||||
|
<div class="card p-3 rounded-4 shadow-sm bg-white mb-4 border-0">
|
||||||
|
<div class="row g-3 align-items-center">
|
||||||
|
<div class="col-12 col-md-5">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light border-0">🔍</span>
|
||||||
|
<input type="text" class="form-control border-0 bg-light rounded-end-pill"
|
||||||
|
placeholder="Hľadať slovo alebo význam (napr. byľ, myš, rieka)..."
|
||||||
|
@bind="searchTerm" @bind:event="oninput" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-4 d-flex gap-1 flex-wrap">
|
||||||
|
<button class="btn btn-sm @(selectedLetter == null ? "btn-primary" : "btn-outline-primary") rounded-pill px-3"
|
||||||
|
@onclick="() => FilterByLetter(null)">
|
||||||
|
Všetky
|
||||||
|
</button>
|
||||||
|
@foreach (var cat in Repository.GetAllCategories())
|
||||||
|
{
|
||||||
|
var l = cat.Letter;
|
||||||
|
<button class="btn btn-sm @(selectedLetter == l ? "btn-primary" : "btn-outline-primary") rounded-pill px-3"
|
||||||
|
@onclick="() => FilterByLetter(l)">
|
||||||
|
@cat.Icon @l
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-3 text-md-end">
|
||||||
|
<div class="form-check form-switch d-inline-block">
|
||||||
|
<input class="form-check-input" type="checkbox" id="archaicOnly" @bind="onlyArchaic" />
|
||||||
|
<label class="form-check-label fw-medium" for="archaicOnly">Iba ťažké a archaizmy ⭐</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zoznam kariet so slovami -->
|
||||||
|
<div class="row g-3">
|
||||||
|
@if (filteredWords.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="col-12 text-center p-5 bg-white rounded-4 shadow-sm">
|
||||||
|
<span class="fs-1">🔎</span>
|
||||||
|
<h4 class="mt-2 fw-bold text-muted">Žiadne slovo nezodpovedá hľadaniu</h4>
|
||||||
|
<p class="text-muted">Skús upraviť filter alebo hľadaný text.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var w in filteredWords)
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="card p-3 rounded-4 shadow-sm border-0 bg-white h-100 cursor-pointer hover-scale transition-all"
|
||||||
|
@onclick="() => Speak(w)">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="fs-2">@w.Emoji</span>
|
||||||
|
<h4 class="fw-bold m-0 text-dark">@w.Word</h4>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-1">
|
||||||
|
<span class="badge bg-secondary rounded-pill">po @w.Letter</span>
|
||||||
|
@if (w.IsArchaicOrDifficult)
|
||||||
|
{
|
||||||
|
<span class="badge bg-warning text-dark rounded-pill" title="Menej známe alebo archaické slovo">⭐ Archaizmus</span>
|
||||||
|
}
|
||||||
|
<button class="btn btn-sm btn-outline-primary rounded-circle p-2 ms-1" title="Vysloviť slovo">
|
||||||
|
🔊
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-primary fw-medium mb-1">
|
||||||
|
@w.Meaning
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small fst-italic mt-auto">
|
||||||
|
„@w.ExampleSentence“
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string searchTerm = string.Empty;
|
||||||
|
private string? selectedLetter = null;
|
||||||
|
private bool onlyArchaic = false;
|
||||||
|
|
||||||
|
private List<WordItem> allWords = new();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
allWords = Repository.GetAllCategories().SelectMany(c => c.Words).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<WordItem> filteredWords
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var result = allWords.AsEnumerable();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(selectedLetter))
|
||||||
|
{
|
||||||
|
result = result.Where(w => w.Letter.Equals(selectedLetter, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyArchaic)
|
||||||
|
{
|
||||||
|
result = result.Where(w => w.IsArchaicOrDifficult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||||
|
{
|
||||||
|
var term = searchTerm.Trim().ToLowerInvariant();
|
||||||
|
result = result.Where(w => w.Word.ToLowerInvariant().Contains(term) ||
|
||||||
|
w.Meaning.ToLowerInvariant().Contains(term) ||
|
||||||
|
w.ExampleSentence.ToLowerInvariant().Contains(term));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterByLetter(string? letter)
|
||||||
|
{
|
||||||
|
selectedLetter = letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Speak(WordItem word)
|
||||||
|
{
|
||||||
|
await Audio.SpeakWordAsync(word.Word);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoHome()
|
||||||
|
{
|
||||||
|
Nav.NavigateTo("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@page "/not-found"
|
||||||
|
@layout MainLayout
|
||||||
|
|
||||||
|
<h3>Not Found</h3>
|
||||||
|
<p>Sorry, the content you are looking for does not exist.</p>
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
@page "/province/{letter}"
|
||||||
|
@inject WordRepository Repository
|
||||||
|
@inject StorageService Storage
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
@inject AudioService Audio
|
||||||
|
|
||||||
|
@if (category == null)
|
||||||
|
{
|
||||||
|
<div class="text-center p-5">
|
||||||
|
<h3>Provincia sa nenašla!</h3>
|
||||||
|
<button class="btn btn-primary rounded-pill px-4 mt-3" @onclick="GoHome">Späť na mapu kráľovstva</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="province-page">
|
||||||
|
<!-- Hlavička provincie -->
|
||||||
|
<div class="card p-4 rounded-4 shadow-sm text-white mb-4 border-0"
|
||||||
|
style="background: @category.BgGradient;">
|
||||||
|
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<button class="btn btn-light rounded-circle shadow p-2 px-3 fw-bold fs-5" @onclick="GoHome" title="Späť na mapu">
|
||||||
|
⬅️
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<span class="fs-1">@category.Icon</span>
|
||||||
|
<h2 class="fw-bold m-0">@category.Title</h2>
|
||||||
|
</div>
|
||||||
|
<p class="m-0 mt-1 opacity-90 fs-6">@category.Subtitle</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
<button class="btn btn-warning rounded-pill px-3 py-2 fw-bold text-dark shadow-sm"
|
||||||
|
@onclick="() => showLexiconModal = true">
|
||||||
|
📖 Slová provincie (@category.Words.Count)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Výber minihry alebo zobrazenie aktívnej hry -->
|
||||||
|
@if (activeMode == GameMode.Select)
|
||||||
|
{
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Karta 1: Vláčik slov -->
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="card h-100 p-4 rounded-4 shadow-sm border-0 bg-white hover-scale transition-all d-flex flex-column justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="fs-1 mb-2">🚂</div>
|
||||||
|
<h4 class="fw-bold text-primary">Vláčik slov & Most</h4>
|
||||||
|
<p class="text-muted small">
|
||||||
|
Nauč sa slová v rytme, doplň chýbajúci vagón a poskladaj celý rad od začiatku po koniec.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary rounded-pill w-100 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetMode(GameMode.Train)">
|
||||||
|
Hrať Vláčik 🚀
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Karta 2: Lov na votrelcov -->
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="card h-100 p-4 rounded-4 shadow-sm border-0 bg-white hover-scale transition-all d-flex flex-column justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="fs-1 mb-2">👾</div>
|
||||||
|
<h4 class="fw-bold text-danger">Lov na votrelcov</h4>
|
||||||
|
<p class="text-muted small">
|
||||||
|
Škriatok Mäkkýš zamiešal medzi vybrané slová slová s mäkkým I. Odhaľ ich rýchlo!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-danger rounded-pill w-100 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetMode(GameMode.Alien)">
|
||||||
|
Loviť votrelcov 🎯
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Karta 3: Kvíz významov -->
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="card h-100 p-4 rounded-4 shadow-sm border-0 bg-white hover-scale transition-all d-flex flex-column justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="fs-1 mb-2">💡</div>
|
||||||
|
<h4 class="fw-bold text-success">Kto som? (Významy)</h4>
|
||||||
|
<p class="text-muted small">
|
||||||
|
Hádanky o tom, čo vybrané slová znamenajú v skutočnom svete. Spoznaj aj staré archaizmy!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success rounded-pill w-100 py-2 fw-bold shadow-sm"
|
||||||
|
@onclick="() => SetMode(GameMode.Quiz)">
|
||||||
|
Hľadať významy 📖
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Karta 4: Súboj so škriatkom Mäkkýšom -->
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="card h-100 p-4 rounded-4 shadow-sm border-0 bg-white hover-scale transition-all d-flex flex-column justify-content-between"
|
||||||
|
style="border: 2px dashed #f59e0b !important;">
|
||||||
|
<div>
|
||||||
|
<div class="fs-1 mb-2">🧙♂️</div>
|
||||||
|
<h4 class="fw-bold text-warning-emphasis">Súboj s bossom</h4>
|
||||||
|
<p class="text-muted small">
|
||||||
|
Prever si všetko, čo si sa naučil. Poraz škriatka a získaj Zlatú Pečať provincie!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-warning rounded-pill w-100 py-2 fw-bold text-dark shadow-sm"
|
||||||
|
@onclick="() => SetMode(GameMode.Boss)">
|
||||||
|
Vyzvať škriatka ⚡
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mb-3">
|
||||||
|
<button class="btn btn-outline-secondary rounded-pill px-3 py-1 fw-medium"
|
||||||
|
@onclick="() => SetMode(GameMode.Select)">
|
||||||
|
⬅️ Návrat k výberu hier provincie
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@switch (activeMode)
|
||||||
|
{
|
||||||
|
case GameMode.Train:
|
||||||
|
<TrainGame Category="category" OnCompleted="HandleTrainCompleted" />
|
||||||
|
break;
|
||||||
|
case GameMode.Alien:
|
||||||
|
<AlienHunterGame Category="category" OnCompleted="HandleAlienCompleted" />
|
||||||
|
break;
|
||||||
|
case GameMode.Quiz:
|
||||||
|
<WordMeaningQuiz Category="category" OnCompleted="HandleQuizCompleted" />
|
||||||
|
break;
|
||||||
|
case GameMode.Boss:
|
||||||
|
<BossChallenge Category="category" OnDefeated="HandleBossDefeated" />
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Modál so zoznamom slov -->
|
||||||
|
@if (showLexiconModal)
|
||||||
|
{
|
||||||
|
<LexiconModal Category="category" OnClose="() => showLexiconModal = false" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Letter { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private LetterCategory? category;
|
||||||
|
private PlayerProgress progress = new();
|
||||||
|
private bool showLexiconModal = false;
|
||||||
|
|
||||||
|
private enum GameMode { Select, Train, Alien, Quiz, Boss }
|
||||||
|
private GameMode activeMode = GameMode.Select;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
category = Repository.GetCategory(Letter);
|
||||||
|
progress = await Storage.LoadProgressAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetMode(GameMode mode)
|
||||||
|
{
|
||||||
|
activeMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoHome()
|
||||||
|
{
|
||||||
|
Nav.NavigateTo("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleTrainCompleted(int stars)
|
||||||
|
{
|
||||||
|
progress.CompletedTrains.Add(category!.Letter);
|
||||||
|
progress.AddStarsForLetter(category.Letter, Math.Max(progress.GetStarsForLetter(category.Letter), 1));
|
||||||
|
await SaveAndCheckUnlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleAlienCompleted(int stars)
|
||||||
|
{
|
||||||
|
progress.CompletedAliens.Add(category!.Letter);
|
||||||
|
progress.AddStarsForLetter(category.Letter, Math.Max(progress.GetStarsForLetter(category.Letter), 2));
|
||||||
|
await SaveAndCheckUnlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleQuizCompleted(int stars)
|
||||||
|
{
|
||||||
|
progress.CompletedQuizzes.Add(category!.Letter);
|
||||||
|
progress.AddStarsForLetter(category.Letter, Math.Max(progress.GetStarsForLetter(category.Letter), 2));
|
||||||
|
await SaveAndCheckUnlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleBossDefeated(bool defeated)
|
||||||
|
{
|
||||||
|
if (defeated)
|
||||||
|
{
|
||||||
|
progress.DefeatedBosses.Add(category!.Letter);
|
||||||
|
progress.AddStarsForLetter(category.Letter, 3);
|
||||||
|
await SaveAndCheckUnlocks();
|
||||||
|
Nav.NavigateTo("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAndCheckUnlocks()
|
||||||
|
{
|
||||||
|
// Odomkni provincie, ktoré spĺňajú podmienku hviezd
|
||||||
|
foreach (var cat in Repository.GetAllCategories())
|
||||||
|
{
|
||||||
|
if (progress.TotalStars >= cat.RequiredStarsToUnlock)
|
||||||
|
{
|
||||||
|
progress.UnlockedLetters.Add(cat.Letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Storage.SaveProgressAsync(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
using Ypsilonkovo;
|
||||||
|
|
||||||
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
builder.RootComponents.Add<App>("#app");
|
||||||
|
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||||
|
|
||||||
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
builder.Services.AddSingleton<Ypsilonkovo.Services.WordRepository>();
|
||||||
|
builder.Services.AddScoped<Ypsilonkovo.Services.AudioService>();
|
||||||
|
builder.Services.AddScoped<Ypsilonkovo.Services.StorageService>();
|
||||||
|
|
||||||
|
await builder.Build().RunAsync();
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
|
"applicationUrl": "http://localhost:5169",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
|
"applicationUrl": "https://localhost:7192;http://localhost:5169",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Ypsilonkovo.Services;
|
||||||
|
|
||||||
|
public class VoiceSettings
|
||||||
|
{
|
||||||
|
public string Gender { get; set; } = "female";
|
||||||
|
public double Speed { get; set; } = 0.74;
|
||||||
|
public string SelectedVoiceURI { get; set; } = string.Empty;
|
||||||
|
public bool AllowCzechFallback { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoiceItemInfo
|
||||||
|
{
|
||||||
|
public string Uri { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Lang { get; set; } = string.Empty;
|
||||||
|
public bool IsSlovak { get; set; }
|
||||||
|
public bool IsCzech { get; set; }
|
||||||
|
public bool IsFemale { get; set; }
|
||||||
|
public bool IsMale { get; set; }
|
||||||
|
public bool IsNatural { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime _js;
|
||||||
|
|
||||||
|
public AudioService(IJSRuntime js)
|
||||||
|
{
|
||||||
|
_js = js;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PlaySuccessAsync()
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.playSuccess"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PlayWrongAsync()
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.playWrong"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PlayFanfareAsync()
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.playFanfare"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PlayBeatAsync(int index)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.playBeat", index); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask SpeakWordAsync(string word)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.speakWord", word); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask SetVoiceGenderAsync(string gender)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.setVoiceGender", gender); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask SetVoiceSpeedAsync(double speed)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.setVoiceSpeed", speed); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask SetSelectedVoiceURIAsync(string uri)
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.setSelectedVoiceURI", uri); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<VoiceSettings> GetVoiceSettingsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<VoiceSettings>("gameAudio.getVoiceSettings");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new VoiceSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<List<VoiceItemInfo>> GetAvailableVoicesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<List<VoiceItemInfo>>("gameAudio.getAvailableVoiceList") ?? new();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask TestVoiceSpeechAsync()
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.testVoiceSpeech"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask LaunchConfettiAsync()
|
||||||
|
{
|
||||||
|
try { await _js.InvokeVoidAsync("gameAudio.launchConfetti"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> ToggleBackgroundMusicAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<bool>("gameAudio.toggleBackgroundMusic");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> IsBackgroundMusicPlayingAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<bool>("gameAudio.isBackgroundMusicPlaying");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<List<TrackInfo>> GetTrackListAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<List<TrackInfo>>("gameAudio.getTrackList") ?? new();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<int> SetTrackAsync(int trackIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<int>("gameAudio.setTrack", trackIndex);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<int> GetCurrentTrackIndexAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _js.InvokeAsync<int>("gameAudio.getCurrentTrackIndex");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrackInfo
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Desc { get; set; } = string.Empty;
|
||||||
|
public int Index { get; set; }
|
||||||
|
public bool IsSelected { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Ypsilonkovo.Models;
|
||||||
|
|
||||||
|
namespace Ypsilonkovo.Services;
|
||||||
|
|
||||||
|
public class StorageService
|
||||||
|
{
|
||||||
|
private const string StorageKey = "ypsilonkovo_player_progress";
|
||||||
|
private readonly IJSRuntime _js;
|
||||||
|
|
||||||
|
public StorageService(IJSRuntime js)
|
||||||
|
{
|
||||||
|
_js = js;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PlayerProgress> LoadProgressAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = await _js.InvokeAsync<string>("localStorage.getItem", StorageKey);
|
||||||
|
if (!string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
var progress = JsonSerializer.Deserialize<PlayerProgress>(json);
|
||||||
|
if (progress != null)
|
||||||
|
{
|
||||||
|
if (progress.UnlockedLetters.Count == 0)
|
||||||
|
progress.UnlockedLetters.Add("B");
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to load progress: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveProgressAsync(PlayerProgress progress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(progress);
|
||||||
|
await _js.InvokeVoidAsync("localStorage.setItem", StorageKey, json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to save progress: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
using Ypsilonkovo.Models;
|
||||||
|
|
||||||
|
namespace Ypsilonkovo.Services;
|
||||||
|
|
||||||
|
public class WordRepository
|
||||||
|
{
|
||||||
|
private readonly List<LetterCategory> _categories;
|
||||||
|
|
||||||
|
public WordRepository()
|
||||||
|
{
|
||||||
|
_categories = new List<LetterCategory>
|
||||||
|
{
|
||||||
|
CreateCategoryB(),
|
||||||
|
CreateCategoryM(),
|
||||||
|
CreateCategoryP(),
|
||||||
|
CreateCategoryR(),
|
||||||
|
CreateCategoryS(),
|
||||||
|
CreateCategoryV(),
|
||||||
|
CreateCategoryZ()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<LetterCategory> GetAllCategories() => _categories;
|
||||||
|
|
||||||
|
public LetterCategory? GetCategory(string letter) =>
|
||||||
|
_categories.FirstOrDefault(c => c.Letter.Equals(letter, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryB()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "B",
|
||||||
|
Title = "Borovicový les (B)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po B podľa PSP",
|
||||||
|
Description = "Zelený borovicový les je domovom divokých býkov a voňavých bylín. Prejdi most a nauč sa celý rad vybraných slov po B v správnom poradí!",
|
||||||
|
Icon = "🌲",
|
||||||
|
ThemeColor = "#16a34a",
|
||||||
|
BgGradient = "linear-gradient(135deg, #166534, #22c55e)",
|
||||||
|
RequiredStarsToUnlock = 0,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1: Začiatok radu
|
||||||
|
new() { Letter = "B", ChunkGroup = 1, Word = "by", Meaning = "Pomocné slovíčko pri želaniach a podmienkach (chcel by, keby)", ExampleSentence = "Rád by som ochutnal lesné jahody.", Emoji = "💭" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 1, Word = "aby", Meaning = "Spojka vyjadrujúca cieľ a dôvod", ExampleSentence = "Učím sa, aby som všetko dobre vedel.", Emoji = "🎯" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 1, Word = "byľ", Meaning = "Tenká stonka bylinky alebo rastliny", ExampleSentence = "Na lúke sa vznášala suchá byľ trávy.", Emoji = "🌱", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "B", ChunkGroup = 1, Word = "bystrý", Meaning = "Rýchly, pohotový a múdry", ExampleSentence = "Bystrý rys spozoroval stopu v snehu.", Emoji = "🦊" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 1, Word = "Bystrica", Meaning = "Mesto Banská Bystrica alebo Považská Bystrica", ExampleSentence = "Banská Bystrica je krásne mesto na strednom Slovensku.", Emoji = "🏰" },
|
||||||
|
|
||||||
|
// Časť 2: Mestá a domov
|
||||||
|
new() { Letter = "B", ChunkGroup = 2, Word = "Bytča", Meaning = "Historické mesto na Považí pri rieke Váh", ExampleSentence = "V meste Bytča stojí známy renesančný kaštieľ.", Emoji = "🏙️" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 2, Word = "byť", Meaning = "Existovať, žiť (Pozor: byť = existovať, biť = udierať!)", ExampleSentence = "Chcem byť slušným a múdrym človekom.", Emoji = "🧍", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "B", ChunkGroup = 2, Word = "nábytok", Meaning = "Stoly, stoličky, skrine a postele v izbe", ExampleSentence = "Do novej izby kúpili drevený nábytok.", Emoji = "🪑" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 2, Word = "bývať", Meaning = "Mať niekde svoj domov", ExampleSentence = "Kde budeš bývať počas letných prázdnin?", Emoji = "🏡" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 2, Word = "byt", Meaning = "Miesto v bytovom dome, kde rodina býva", ExampleSentence = "Náš útulný byt má tri pekné izby.", Emoji = "🏠" },
|
||||||
|
|
||||||
|
// Časť 3: Obydlie a zvieratá
|
||||||
|
new() { Letter = "B", ChunkGroup = 3, Word = "bydlisko", Meaning = "Oficiálna adresa, kde človek býva", ExampleSentence = "Moje trvalé bydlisko je v Bratislave.", Emoji = "📍" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 3, Word = "príbytok", Meaning = "Domček, chalúpka alebo skrýša zvieratka", ExampleSentence = "Veverička si v strome spravila teplý príbytok.", Emoji = "🛖" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 3, Word = "dobytok", Meaning = "Domáce hospodárske zvieratá (kravy, kozy, ovce)", ExampleSentence = "Bača vyhnal dobytok na zelenú lúku.", Emoji = "🐄" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 3, Word = "kobyla", Meaning = "Dospelá samica koňa", ExampleSentence = "Hnedá kobyla cválala popri plote.", Emoji = "🐴" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 3, Word = "obyčaj", Meaning = "Starodávna tradícia, zvyk", ExampleSentence = "Oblievačka je pekná veľkonočná obyčaj.", Emoji = "🎀", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 4: Príroda a boj
|
||||||
|
new() { Letter = "B", ChunkGroup = 4, Word = "býk", Meaning = "Veľký a silný samec kravy", ExampleSentence = "Statný býk oddychoval pod stromom.", Emoji = "🐂" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 4, Word = "bylina", Meaning = "Rastlinka s mäkkou zelenou stonkou (liečivka)", ExampleSentence = "Harmanček je voňavá liečivá bylina.", Emoji = "🌿" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 4, Word = "bydlo", Meaning = "Bývanie, domov (Pozor: bydlo = domov, bidlo = žrď!)", ExampleSentence = "Zvieratká si našli bezpečné bydlo v lese.", Emoji = "🪵", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "B", ChunkGroup = 4, Word = "dobyť", Meaning = "Zmocniť sa hradu alebo územia v boji", ExampleSentence = "Rytieri dokázali dobyť nedobytný hrad.", Emoji = "⚔️", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "B", ChunkGroup = 4, Word = "odbyt", Meaning = "Predaj tovaru, po ktorom je dopyt", ExampleSentence = "Pekáreň mala výborný odbyt na čerstvý chlieb.", Emoji = "🛒", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 5: Záver radu
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "byvol", Meaning = "Veľké rohaté zviera žijúce pri riekach a močiaroch", ExampleSentence = "Divý byvol sa rád kúpe vo vode.", Emoji = "🦬" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "bytosť", Meaning = "Každý živý tvor, človek alebo rozprávková postava", ExampleSentence = "Víla je čarovná rozprávková bytosť.", Emoji = "🧚" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "bývalý", Meaning = "Niekto alebo niečo, čo bolo v minulosti", ExampleSentence = "Stretol som bývalého spolužiaka zo škôlky.", Emoji = "⏳" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "úbytok", Meaning = "Zmenšenie množstva alebo úbytok vody v rieke", ExampleSentence = "V suchom lete nastal úbytok vody v jazere.", Emoji = "📉" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "prebytok", Meaning = "Hojnosť, keď máme niečoho viac než dosť", ExampleSentence = "Po bohatej úrode mali prebytok jabĺk.", Emoji = "🍎" },
|
||||||
|
new() { Letter = "B", ChunkGroup = 5, Word = "zbytočný", Meaning = "Nepotrebný, taký čo nepomôže", ExampleSentence = "Hnev a plač bol celkom zbytočný.", Emoji = "🚫" }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "bič", IsAlien = true, Reason = "Píše sa s mäkkým 'i' (nie je vybrané slovo)!" },
|
||||||
|
new() { Word = "bicykel", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "bitka", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "biela", IsAlien = true, Reason = "Má dvojhlásku 'ie'!" },
|
||||||
|
new() { Word = "bylina", IsAlien = false, Reason = "Správne! Bylina je vybrané slovo po B." },
|
||||||
|
new() { Word = "kobyla", IsAlien = false, Reason = "Správne! Kobyla je vybrané slovo po B." },
|
||||||
|
new() { Word = "bystrý", IsAlien = false, Reason = "Správne! Bystrý je vybrané slovo po B." },
|
||||||
|
new() { Word = "býk", IsAlien = false, Reason = "Správne! Býk je vybrané slovo po B." },
|
||||||
|
new() { Word = "Bytča", IsAlien = false, Reason = "Správne! Bytča je vybrané slovo po B." },
|
||||||
|
new() { Word = "dobytok", IsAlien = false, Reason = "Správne! Dobytok je vybrané slovo po B." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryM()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "M",
|
||||||
|
Title = "Močiar múdrosti (M)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po M podľa PSP",
|
||||||
|
Description = "Priprav si myseľ, zober mydlo a nauč sa vybrané slová po M v presnom poradí!",
|
||||||
|
Icon = "🐸",
|
||||||
|
ThemeColor = "#0284c7",
|
||||||
|
BgGradient = "linear-gradient(135deg, #0369a1, #38bdf8)",
|
||||||
|
RequiredStarsToUnlock = 2,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "my", Meaning = "My všetci spolu (rodina, kamaráti)", ExampleSentence = "My sa radi učíme nové veci.", Emoji = "👥" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "mykať sa", Meaning = "Trhať sebou alebo prudko potiahnuť", ExampleSentence = "Chlapec mykal plecami od prekvapenia.", Emoji = "⚡" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "mýliť sa", Meaning = "Robiť chybu, nemať pravdu", ExampleSentence = "Každý človek sa občas môže mýliť.", Emoji = "🧩" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "myslieť", Meaning = "Rozmýšľať hlavou", ExampleSentence = "Pred každou odpoveďou treba dobre myslieť.", Emoji = "🧠" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "myšlienka", Meaning = "Nápad, ktorý vznikne v hlave", ExampleSentence = "Blysla mu v hlave výborná myšlienka.", Emoji = "💡" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 1, Word = "myseľ", Meaning = "Vnútorný svet našich myšlienok", ExampleSentence = "Veselá myseľ prináša dobrú náladu.", Emoji = "✨" },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "umývať sa", Meaning = "Očisťovať sa vodou a mydlom", ExampleSentence = "Ráno sa všetci ideme umývať.", Emoji = "🚿" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "mydlo", Meaning = "Voňavá kocka alebo gél na umývanie", ExampleSentence = "Mydlo vytvára jemnú bielu penu.", Emoji = "🧼" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "myš", Meaning = "Malé sivé zvieratko", ExampleSentence = "Mačka zbadala pod stolom malú myš.", Emoji = "🐭" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "šmýkať sa", Meaning = "Kĺzať sa na ľade alebo tobogane", ExampleSentence = "Deti sa na ihrisku rady šmýkajú.", Emoji = "🛝" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "hmyz", Meaning = "Drobné živočíchy (motýle, chrobáky, včielky)", ExampleSentence = "Na voňavej lúke bzučal užitočný hmyz.", Emoji = "🐝" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 2, Word = "žmýkať", Meaning = "Stláčaním zbavovať vodu z bielizne", ExampleSentence = "Mama musela mokré tričko poriadne vyžmýkať.", Emoji = "🧺" },
|
||||||
|
|
||||||
|
// Časť 3
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "priemysel", Meaning = "Továrne a výroba áut či strojov", ExampleSentence = "Na Slovensku máme rozvinutý automobilový priemysel.", Emoji = "🏭", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "Myjava", Meaning = "Slovenské mesto a rieka v kopaničiarskom kraji", ExampleSentence = "Cez Myjavu preteká čistá riečka.", Emoji = "🏘️" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "mýto", Meaning = "Poplatok za prechod cez diaľnicu alebo most", ExampleSentence = "Nákladné autá platia na diaľniciach mýto.", Emoji = "🪙", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "mys", Meaning = "Úzky výbežok pevniny hlboko do mora", ExampleSentence = "Na skalnatom myse stál starý maják.", Emoji = "🌊", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "zamykať", Meaning = "Zatvárať dvere na kľúč", ExampleSentence = "Nezabudni pred odchodom zamykať dvere.", Emoji = "🔒" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 3, Word = "pomykov", Meaning = "Rozpaky, zmätok v hlave", ExampleSentence = "Po nečakanej otázke ostal na chvíľu v pomykove.", Emoji = "😵💫", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 4
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "hmýriť sa", Meaning = "Husto sa pohybovať sem a tam", ExampleSentence = "V mravenisku sa hmýrili stovky mravcov.", Emoji = "🐜", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "šmyk", Meaning = "Nečakané pošmyknutie sa", ExampleSentence = "Auto dostalo na zľadovatelej ceste šmyk.", Emoji = "🚗" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "priesmyk", Meaning = "Úzke horské sedlo medzi štítmi", ExampleSentence = "Cez vysokohorský priesmyk fúkal studený vietor.", Emoji = "🏔️", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "omyl", Meaning = "Chyba z nepozornosti", ExampleSentence = "Ospravedlnil sa za malý omyl.", Emoji = "❌" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "zmysel", Meaning = "Význam slova alebo zmysly (zrak, sluch)", ExampleSentence = "Táto veta dáva pekný zmysel.", Emoji = "👁️" },
|
||||||
|
new() { Letter = "M", ChunkGroup = 4, Word = "pomyje", Meaning = "Voda so zvyškami jedál na kŕmenie prasiatok", ExampleSentence = "V minulosti na dedine dávali pomyje prasiatkam.", Emoji = "🥣", IsArchaicOrDifficult = true }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "miska", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "minúta", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "milý", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "minca", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "myš", IsAlien = false, Reason = "Správne! Myš je vybrané slovo po M." },
|
||||||
|
new() { Word = "mydlo", IsAlien = false, Reason = "Správne! Mydlo je vybrané slovo po M." },
|
||||||
|
new() { Word = "hmyz", IsAlien = false, Reason = "Správne! Hmyz je vybrané slovo po M." },
|
||||||
|
new() { Word = "šmýkať sa", IsAlien = false, Reason = "Správne! Šmýkať sa je vybrané slovo po M." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryP()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "P",
|
||||||
|
Title = "Pralesy pyšného páva (P)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po P podľa PSP",
|
||||||
|
Description = "Nauč sa rad vybraných slov po P od pýchy až po pyžamo!",
|
||||||
|
Icon = "🦚",
|
||||||
|
ThemeColor = "#d97706",
|
||||||
|
BgGradient = "linear-gradient(135deg, #b45309, #f59e0b)",
|
||||||
|
RequiredStarsToUnlock = 4,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "P", ChunkGroup = 1, Word = "pýcha", Meaning = "Nafúkanosť, keď si niekto namýšľa priveľa", ExampleSentence = "Pýcha predchádza pád, varuje múdre príslovie.", Emoji = "👑" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 1, Word = "pýšiť sa", Meaning = "Chváliť sa niečím pred ostatnými", ExampleSentence = "Zámok sa pýši krásnou záhradou.", Emoji = "📯" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 1, Word = "pyšný", Meaning = "Hrdý a namyslený človek", ExampleSentence = "Páv je pyšný na svoj farebný chvost.", Emoji = "🦚" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 1, Word = "prepych", Meaning = "Veľký luxus a bohatstvo", ExampleSentence = "Kráľovská rodina žila v prepychu.", Emoji = "🏰", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "P", ChunkGroup = 2, Word = "pykať", Meaning = "Niesť trest za nesprávne správanie", ExampleSentence = "Za klamstvo musel pykať.", Emoji = "⏳", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "P", ChunkGroup = 2, Word = "pýr", Meaning = "Hustá burina rastúca na poli a v záhrade", ExampleSentence = "Starý otec vytrhával v záhrade pýr.", Emoji = "🌾", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "P", ChunkGroup = 2, Word = "pysk", Meaning = "Papuľa alebo ústa zvieratka (psa, koňa)", ExampleSentence = "Pes si oblízal vlhký čierny pysk.", Emoji = "🐕" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 2, Word = "pytliak", Meaning = "Človek, ktorý nelegálne pytliači v lese", ExampleSentence = "Horár odhalil v lese pytliaka.", Emoji = "🏹", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 3
|
||||||
|
new() { Letter = "P", ChunkGroup = 3, Word = "kopyto", Meaning = "Tvrdé chodidlo koňa či srnky", ExampleSentence = "Kováč starostlivo očistil koňovi kopyto.", Emoji = "🐎" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 3, Word = "pýtať sa", Meaning = "Klásť niekomu otázku", ExampleSentence = "Kto sa pýta, ten sa veľa dozvie.", Emoji = "❓" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 3, Word = "opýtať sa", Meaning = "Obrátiť sa s otázkou na učiteľa", ExampleSentence = "Chcem sa opýtať na cestu k zámku.", Emoji = "🙋" },
|
||||||
|
new() { Letter = "P", ChunkGroup = 3, Word = "pyžamo", Meaning = "Pohodlné oblečenie na spanie", ExampleSentence = "Večer si obliekam čisté pyžamo.", Emoji = "🌙" }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "pivo", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "píšťalka", IsAlien = true, Reason = "Píše sa s mäkkým 'í'!" },
|
||||||
|
new() { Word = "pilot", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "pieseň", IsAlien = true, Reason = "Má dvojhlásku 'ie'!" },
|
||||||
|
new() { Word = "pyšný", IsAlien = false, Reason = "Správne! Pyšný je vybrané slovo po P." },
|
||||||
|
new() { Word = "kopyto", IsAlien = false, Reason = "Správne! Kopyto je vybrané slovo po P." },
|
||||||
|
new() { Word = "pýtať sa", IsAlien = false, Reason = "Správne! Pýtať sa je vybrané slovo po P." },
|
||||||
|
new() { Word = "pyžamo", IsAlien = false, Reason = "Správne! Pyžamo je vybrané slovo po P." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryR()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "R",
|
||||||
|
Title = "Rieka rýchlych rýb (R)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po R podľa PSP",
|
||||||
|
Description = "Prejdi dravú rieku a spoznaj všetky vybrané slová po R!",
|
||||||
|
Icon = "🐟",
|
||||||
|
ThemeColor = "#0891b2",
|
||||||
|
BgGradient = "linear-gradient(135deg, #0e7490, #06b6d4)",
|
||||||
|
RequiredStarsToUnlock = 6,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "R", ChunkGroup = 1, Word = "ryba", Meaning = "Živočích žijúci vo vode", ExampleSentence = "V potoku plávala strieborná ryba.", Emoji = "🐟" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 1, Word = "rýchly", Meaning = "Pohybujúci sa veľkou rýchlosťou", ExampleSentence = "Gepard je najrýchlejší bežec na svete.", Emoji = "⚡" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 1, Word = "rys", Meaning = "Plachá mačkovitá šelma so štetinkami na ušiach", ExampleSentence = "V tatranských lesoch žije chránený rys.", Emoji = "🐱" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 1, Word = "ryť", Meaning = "Robiť jamy a brázdy v zemi rypákom či náradím", ExampleSentence = "Diviaky rady ryjú v mäkkej zemi.", Emoji = "🐗" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 1, Word = "rýpať", Meaning = "Vŕtať do niečoho alebo dobiedzať", ExampleSentence = "Prestaň rýpať prstom do steny.", Emoji = "👉" },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "R", ChunkGroup = 2, Word = "hrýzť", Meaning = "Drviť zubami jedlo", ExampleSentence = "Zajac začal hrýzť sladkú mrkvičku.", Emoji = "🥕" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 2, Word = "kryť", Meaning = "Chrániť pred dažďom alebo vetrom", ExampleSentence = "Dáždnik ťa bude kryť pred lejakom.", Emoji = "☂️" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 2, Word = "skryť", Meaning = "Uložiť na bezpečné tajné miesto", ExampleSentence = "Poklad sa podarilo skryť do jaskyne.", Emoji = "🗝️" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 2, Word = "koryto", Meaning = "Dno, kade tečie rieka, alebo drevená nádoba", ExampleSentence = "Rieka tiekla vo svojom hlbokom koryte.", Emoji = "🪵" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 2, Word = "korytnačka", Meaning = "Pomalé zvieratko s pevným pancierom", ExampleSentence = "Korytnačka pomaly kráčala po piesku.", Emoji = "🐢" },
|
||||||
|
|
||||||
|
// Časť 3
|
||||||
|
new() { Letter = "R", ChunkGroup = 3, Word = "strýko", Meaning = "Brat matky alebo otca", ExampleSentence = "Strýko ma zobral na výlet do hôr.", Emoji = "🧔" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 3, Word = "ryža", Meaning = "Biele obilné zrnká na jedenie", ExampleSentence = "Na obed máme kuracie mäsko a ryžu.", Emoji = "🍚" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 3, Word = "bryndza", Meaning = "Tradičný slovenský solený ovčí syr", ExampleSentence = "Bryndzové halušky sú naše národné jedlo.", Emoji = "🥟" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 3, Word = "rýdzi", Meaning = "Úplne čistý bez prímesí (rýdze zlato)", ExampleSentence = "Prsteň bol vyrobený z rýdzeho zlata.", Emoji = "✨", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "R", ChunkGroup = 3, Word = "rýľ", Meaning = "Náradie na rýľovanie a kopanie v záhrade", ExampleSentence = "Otec rýľom prekopal záhon.", Emoji = "🪴" },
|
||||||
|
|
||||||
|
// Časť 4
|
||||||
|
new() { Letter = "R", ChunkGroup = 4, Word = "ryčať", Meaning = "Vydávať silný hlasný rev (jeleň v ruji)", ExampleSentence = "Z hory bolo počuť ryčať divé zviera.", Emoji = "📢" },
|
||||||
|
new() { Letter = "R", ChunkGroup = 4, Word = "ryha", Meaning = "Dlhá úzka ryha či čiara vyškriabaná v materiáli", ExampleSentence = "V dreve ostala hlboká ryha.", Emoji = "📏", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "R", ChunkGroup = 4, Word = "kryha", Meaning = "Veľký kus plávajúceho ľadu", ExampleSentence = "Na rieke plávala studená ľadová kryha.", Emoji = "🧊", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "R", ChunkGroup = 4, Word = "poryv", Meaning = "Prudký náhly závan vetra", ExampleSentence = "Silný poryv vetra zatriasol oknom.", Emoji = "💨", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "R", ChunkGroup = 4, Word = "trýzniť", Meaning = "Spôsobovať veľké trápenie a bolesť", ExampleSentence = "Nesmú sa trýzniť zvieratká!", Emoji = "💔", IsArchaicOrDifficult = true }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "rieka", IsAlien = true, Reason = "Má dvojhlásku 'ie'!" },
|
||||||
|
new() { Word = "ringloty", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "rizoto", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "ríbezľa", IsAlien = true, Reason = "Píše sa s mäkkým 'í'!" },
|
||||||
|
new() { Word = "ryba", IsAlien = false, Reason = "Správne! Ryba je vybrané slovo po R." },
|
||||||
|
new() { Word = "rýchly", IsAlien = false, Reason = "Správne! Rýchly je vybrané slovo po R." },
|
||||||
|
new() { Word = "korytnačka", IsAlien = false, Reason = "Správne! Korytnačka je vybrané slovo po R." },
|
||||||
|
new() { Word = "rýľ", IsAlien = false, Reason = "Správne! Rýľ je vybrané slovo po R." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryS()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "S",
|
||||||
|
Title = "Slnečná sýpka (S)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po S podľa PSP",
|
||||||
|
Description = "Zvládni rad vybraných slov od syna a syra až po sychravý deň!",
|
||||||
|
Icon = "🧀",
|
||||||
|
ThemeColor = "#eab308",
|
||||||
|
BgGradient = "linear-gradient(135deg, #ca8a04, #facc15)",
|
||||||
|
RequiredStarsToUnlock = 8,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "S", ChunkGroup = 1, Word = "syn", Meaning = "Chlapec vo vzťahu k rodičom", ExampleSentence = "Otec a syn spolu postavili vtáčiu búdku.", Emoji = "👦" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 1, Word = "syr", Meaning = "Chutná dobrota z mlieka", ExampleSentence = "Na chlieb si dám plátok syra.", Emoji = "🧀" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 1, Word = "sýty", Meaning = "Dobre najedený, necítiaci hlad", ExampleSentence = "Po obede bol každý úplne sýty.", Emoji = "😋" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 1, Word = "nasýtiť sa", Meaning = "Dobre sa napapať", ExampleSentence = "Všetci sa dosýta nasýtili.", Emoji = "🍲" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 1, Word = "sypať", Meaning = "Nechať padať drobné zrnká (soľ, piesok)", ExampleSentence = "V zime cestári sypú soľ na cesty.", Emoji = "🧂" },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "S", ChunkGroup = 2, Word = "sypký", Meaning = "Rozpadávajúci sa na zrnká (sypký sneh)", ExampleSentence = "Na kopci ležal čerstvý sypký sneh.", Emoji = "❄️" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 2, Word = "syseľ", Meaning = "Roztomilý poľný hlodavec, ktorý panáčkuje na lúke", ExampleSentence = "Na lúke pri Muráni pobehoval malý syseľ.", Emoji = "🐿️" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 2, Word = "syčať", Meaning = "Vydávať zvuk sss (had, unikajúca para)", ExampleSentence = "Z kanvice začala syčať horúca para.", Emoji = "🐍" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 2, Word = "sýkorka", Meaning = "Milý spevavý vtáčik so žltým bruškom", ExampleSentence = "Do kŕmidla priletela hladná sýkorka.", Emoji = "🐦" },
|
||||||
|
|
||||||
|
// Časť 3
|
||||||
|
new() { Letter = "S", ChunkGroup = 3, Word = "sychravý", Meaning = "Studený, hmlistý a vlhký jesenný deň", ExampleSentence = "V sychravom počasí nosíme teplý sveter.", Emoji = "🌧️", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "S", ChunkGroup = 3, Word = "osýpky", Meaning = "Detská choroba s červenými bodkami", ExampleSentence = "Očkovanie chráni deti pred osýpkami.", Emoji = "🤒", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "S", ChunkGroup = 3, Word = "vysychať", Meaning = "Strácať vodu a vlhkosť v suchu", ExampleSentence = "V horúcom lete začal potok vysychať.", Emoji = "☀️" },
|
||||||
|
new() { Letter = "S", ChunkGroup = 3, Word = "sykať", Meaning = "Vydávať syčivý zvuk pri náhlej bolesti", ExampleSentence = "Pri popálení prsta začal od bolesti sykať.", Emoji = "😣", IsArchaicOrDifficult = true }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "sito", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "sirup", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "sivý", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "silný", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "syn", IsAlien = false, Reason = "Správne! Syn je vybrané slovo po S." },
|
||||||
|
new() { Word = "syr", IsAlien = false, Reason = "Správne! Syr je vybrané slovo po S." },
|
||||||
|
new() { Word = "sýkorka", IsAlien = false, Reason = "Správne! Sýkorka je vybrané slovo po S." },
|
||||||
|
new() { Word = "syseľ", IsAlien = false, Reason = "Správne! Syseľ je vybrané slovo po S." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryV()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "V",
|
||||||
|
Title = "Veterná vyhňa (V)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po V podľa PSP",
|
||||||
|
Description = "Nauč sa rad vybraných slov od vysokého štítu až po výťah!",
|
||||||
|
Icon = "🔥",
|
||||||
|
ThemeColor = "#ea580c",
|
||||||
|
BgGradient = "linear-gradient(135deg, #c2410c, #f97316)",
|
||||||
|
RequiredStarsToUnlock = 10,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "V", ChunkGroup = 1, Word = "vysoký", Meaning = "Majúci veľkú výšku od zeme nahor", ExampleSentence = "Vysoký topoľ sa týčil k nebu.", Emoji = "🌲" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 1, Word = "zvyk", Meaning = "Pravidelná činnosť, tradícia", ExampleSentence = "Ranné cvičenie je zdravý zvyk.", Emoji = "📖" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 1, Word = "vy", Meaning = "Zámeno pre viac osôb alebo úctivé oslovenie", ExampleSentence = "Vy ste veľmi šikovní žiaci!", Emoji = "👥" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 1, Word = "vykať", Meaning = "Slušne oslovovať dospelých slovom Vy", ExampleSentence = "Dospelým na ulici vždy úctivo vykáme.", Emoji = "🤝" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 1, Word = "výr", Meaning = "Najväčšia nočná sova (Pozor: výr = sova, vír = krútňava vody!)", ExampleSentence = "V tichej noci lovil múdry výr skalný.", Emoji = "🦉", IsArchaicOrDifficult = true },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "V", ChunkGroup = 2, Word = "výskať", Meaning = "Radostne a hlasno kričať od radosti", ExampleSentence = "Deti od šťastia začali radostne výskať.", Emoji = "🥳" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 2, Word = "vyť", Meaning = "Ťahavo zavýjať (Pozor: vyť = vlk vyje, viť = pliesť veniec!)", ExampleSentence = "Za splnu mesiaca bolo počuť vyť vlka.", Emoji = "🐺", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "V", ChunkGroup = 2, Word = "vy-", Meaning = "Slovenská predpona (vysypať, vybrať, vymyslieť)", ExampleSentence = "Predpona vy- tvorí mnoho pekných slov.", Emoji = "🔤" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 2, Word = "vyžla", Meaning = "Plemeno elegantného loveckého psa", ExampleSentence = "Zlatistá maďarská vyžla rýchlo bežala cez lúku.", Emoji = "🐕", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "V", ChunkGroup = 2, Word = "vydra", Meaning = "Šikovné vodné zvieratko žijúce pri čistých riekach", ExampleSentence = "Riečna vydra sa obratne potápala za rybou.", Emoji = "🦦" },
|
||||||
|
|
||||||
|
// Časť 3
|
||||||
|
new() { Letter = "V", ChunkGroup = 3, Word = "vyhňa", Meaning = "Kováčska ohnivá pec na spracovanie železa", ExampleSentence = "Z kováčskej vyhne lietali žeravé iskry.", Emoji = "⚒️", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "V", ChunkGroup = 3, Word = "výsosť", Meaning = "Vznešený kráľovský titul (Jeho Výsosť)", ExampleSentence = "Kráľovská Výsosť privítala hostí na hrade.", Emoji = "👑", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "V", ChunkGroup = 3, Word = "zvyšok", Meaning = "To, čo zostalo po rozdelení", ExampleSentence = "Zvyšok koláča odložili do chladničky.", Emoji = "🍰" },
|
||||||
|
new() { Letter = "V", ChunkGroup = 3, Word = "výťah", Meaning = "Kabína na elektrický pohon na prepravu ľudí v budove", ExampleSentence = "Na piate poschodie sme sa odviezli výťahom.", Emoji = "🛗" }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "vidly", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "víla", IsAlien = true, Reason = "Píše sa s mäkkým 'í'!" },
|
||||||
|
new() { Word = "víťaz", IsAlien = true, Reason = "Píše sa s mäkkým 'í'!" },
|
||||||
|
new() { Word = "vietor", IsAlien = true, Reason = "Má dvojhlásku 'ie'!" },
|
||||||
|
new() { Word = "vysoký", IsAlien = false, Reason = "Správne! Vysoký je vybrané slovo po V." },
|
||||||
|
new() { Word = "vydra", IsAlien = false, Reason = "Správne! Vydra je vybrané slovo po V." },
|
||||||
|
new() { Word = "výr", IsAlien = false, Reason = "Správne! Výr je vybrané slovo po V." },
|
||||||
|
new() { Word = "zvyk", IsAlien = false, Reason = "Správne! Zvyk je vybrané slovo po V." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LetterCategory CreateCategoryZ()
|
||||||
|
{
|
||||||
|
return new LetterCategory
|
||||||
|
{
|
||||||
|
Letter = "Z",
|
||||||
|
Title = "Zelené zúbky (Z)",
|
||||||
|
Subtitle = "Kánonický rad vybraných slov po Z podľa PSP",
|
||||||
|
Description = "Dokonči poslednú provinciu kráľovstva Ypsilonkovo!",
|
||||||
|
Icon = "👅",
|
||||||
|
ThemeColor = "#7c3aed",
|
||||||
|
BgGradient = "linear-gradient(135deg, #6d28d9, #8b5cf6)",
|
||||||
|
RequiredStarsToUnlock = 12,
|
||||||
|
Words = new List<WordItem>
|
||||||
|
{
|
||||||
|
// Časť 1
|
||||||
|
new() { Letter = "Z", ChunkGroup = 1, Word = "jazyk", Meaning = "Sval v ústach, alebo slovenský jazyk", ExampleSentence = "Slovenský jazyk je náš rodný jazyk.", Emoji = "👅" },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 1, Word = "jazykový", Meaning = "Týkajúci sa jazyka a slov", ExampleSentence = "Jazykový krúžok nás veľmi baví.", Emoji = "📚" },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 1, Word = "jazyčnica", Meaning = "Ukecaná, uvravená klebetná osoba", ExampleSentence = "Suseda bola známa jazyčnica.", Emoji = "🗣️", IsArchaicOrDifficult = true },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 1, Word = "nazývať sa", Meaning = "Mať pomenovanie, volať sa", ExampleSentence = "Naša vlasť sa nazýva Slovensko.", Emoji = "🌍" },
|
||||||
|
|
||||||
|
// Časť 2
|
||||||
|
new() { Letter = "Z", ChunkGroup = 2, Word = "ozývať sa", Meaning = "Vydávať zvuk, dávať o sebe vedieť", ExampleSentence = "Z doliny sa začala ozývať ozvena.", Emoji = "📣" },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 2, Word = "prezývať", Meaning = "Dávať niekomu prezývku", ExampleSentence = "Kamaráti ho prezývali Bystrík.", Emoji = "🏷️" },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 2, Word = "vyzývať", Meaning = "Vyzývať niekoho do hry či súťaže", ExampleSentence = "Hráč vyzýva súpera na priateľský duel.", Emoji = "⚔️" },
|
||||||
|
new() { Letter = "Z", ChunkGroup = 2, Word = "pozývať", Meaning = "Slušne volať hostí na návštevu", ExampleSentence = "Pozývam ťa na moju narodeninovú oslavu.", Emoji = "💌" }
|
||||||
|
},
|
||||||
|
AlienPool = new List<AlienItem>
|
||||||
|
{
|
||||||
|
new() { Word = "zima", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "zips", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "zisk", IsAlien = true, Reason = "Píše sa s mäkkým 'i'!" },
|
||||||
|
new() { Word = "zrkadlo", IsAlien = true, Reason = "V slove zrkadlo nie je y/i po z!" },
|
||||||
|
new() { Word = "jazyk", IsAlien = false, Reason = "Správne! Jazyk je vybrané slovo po Z." },
|
||||||
|
new() { Word = "ozývať sa", IsAlien = false, Reason = "Správne! Ozývať sa je vybrané slovo po Z." },
|
||||||
|
new() { Word = "nazývať sa", IsAlien = false, Reason = "Správne! Nazývať sa je vybrané slovo po Z." },
|
||||||
|
new() { Word = "pozývať", IsAlien = false, Reason = "Správne! Pozývať je vybrané slovo po Z." }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.11" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
@using System.Net.Http
|
||||||
|
@using System.Net.Http.Json
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
|
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
@using Ypsilonkovo
|
||||||
|
@using Ypsilonkovo.Layout
|
||||||
|
@using Ypsilonkovo.Models
|
||||||
|
@using Ypsilonkovo.Services
|
||||||
|
@using Ypsilonkovo.Components
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user