1012 lines
44 KiB
JavaScript
1012 lines
44 KiB
JavaScript
/* =========================================================================
|
||
KUCHAŘKA KAMILY PEŠEK // PASCAL 7.0 WEB ENGINE (331 RECEPTŮ & 568 JÍDEL)
|
||
Kulinářská paměť Kamily Pešek & Software-salon ALTÁN (Helena Heroutová)
|
||
========================================================================= */
|
||
|
||
// Audio engine (Retro PC Speaker via Web Audio API)
|
||
let audioCtx = null;
|
||
let soundEnabled = true;
|
||
|
||
function playSpeaker(freq = 800, duration = 0.04, type = 'square') {
|
||
if (!soundEnabled) return;
|
||
try {
|
||
if (!audioCtx) {
|
||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||
}
|
||
if (audioCtx.state === 'suspended') {
|
||
audioCtx.resume();
|
||
}
|
||
const osc = audioCtx.createOscillator();
|
||
const gain = audioCtx.createGain();
|
||
osc.type = type;
|
||
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
||
gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
|
||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
|
||
osc.connect(gain);
|
||
gain.connect(audioCtx.destination);
|
||
osc.start();
|
||
osc.stop(audioCtx.currentTime + duration);
|
||
} catch (e) {}
|
||
}
|
||
|
||
// Data references loaded from data.js
|
||
const KNIHA_RECEPTU = window.KNIHA_RECEPTU || [];
|
||
const HISTORICKA_JIDLA = window.HISTORICKA_JIDLA || [];
|
||
|
||
// App State
|
||
let currentView = 'menu'; // 'menu', 'recept', 'recepty_list', 'hledat', 'suroviny', 'davno', 'oblibena', 'kronika', 'altan'
|
||
let currentRecipeId = 1;
|
||
let currentCategory = 'Vše';
|
||
let listPage = 0;
|
||
const PAGE_SIZE = 12;
|
||
|
||
const screenEl = document.getElementById('screen-content');
|
||
const winTitleEl = document.getElementById('win-title');
|
||
const timeEl = document.getElementById('time-display');
|
||
|
||
// Clock
|
||
setInterval(() => {
|
||
const d = new Date();
|
||
timeEl.textContent = d.toTimeString().split(' ')[0];
|
||
}, 1000);
|
||
|
||
// =========================================================================
|
||
// CZECH TEXT NORMALIZATION & FULLTEXT SEARCH ENGINE
|
||
// =========================================================================
|
||
|
||
// Normalizace: malá písmena, odstranění diakritiky
|
||
function norm(str) {
|
||
return (str || "").toLowerCase()
|
||
.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||
}
|
||
|
||
// Český lehký stemmer (odstranění pádových a slovesných koncovek pro dokonalou flexi)
|
||
function stem(word) {
|
||
let w = norm(word).replace(/[^a-z0-9]/g, "");
|
||
if (w.length <= 3) return w;
|
||
// Dlouhé přídavné a slovesné koncovky
|
||
w = w.replace(/(ovy|ova|ove|ymi|ych|ich|ovi|ami|ach|ech|eho|emu|ajici|ejici)$/, "");
|
||
if (w.length > 3) {
|
||
// Slovesa a podstatná jména
|
||
w = w.replace(/(ime|ite|eme|ete|aji|eji|ala|alo|ali|ila|ilo|ili|ula|ulo|uli)$/, "");
|
||
}
|
||
if (w.length > 3) {
|
||
// Dvojpísmenné koncovky
|
||
w = w.replace(/(em|ou|am|um|ym|im|ky|ka|ku|ek|ce|ci|ho|mu|mi|es|as|at|et|it|ut|ti|al|il|ul|ne|na|ny)$/, "");
|
||
}
|
||
if (w.length > 3) {
|
||
// Krátké samohlásky
|
||
w = w.replace(/[aeiouy]$/, "");
|
||
}
|
||
return w;
|
||
}
|
||
|
||
// Rozdělení dotazu na čistá slova
|
||
function tokenize(str) {
|
||
return norm(str)
|
||
.split(/[\s,.;:()!?"'\/\\+*#%&<=>[\]{}|~_`^$]+/)
|
||
.filter(t => t.length > 0);
|
||
}
|
||
|
||
// Kontrola shody slova:
|
||
function wordMatches(targetWord, queryWord, queryStem) {
|
||
const tw = norm(targetWord);
|
||
if (tw === queryWord) return true;
|
||
if (tw.startsWith(queryWord) || queryWord.startsWith(tw)) return true;
|
||
|
||
const ts = stem(targetWord);
|
||
if (ts === queryStem) return true;
|
||
if (queryStem.length >= 3 && ts.startsWith(queryStem)) return true;
|
||
if (ts.length >= 3 && queryStem.startsWith(ts)) return true;
|
||
return false;
|
||
}
|
||
|
||
// Zvýraznění shod v textu (HTML mark)
|
||
function highlightMatches(text, query) {
|
||
if (!query || !text) return text;
|
||
const tokens = tokenize(query).filter(t => t.length > 1);
|
||
if (tokens.length === 0) return text;
|
||
|
||
const stems = tokens.map(stem);
|
||
const parts = text.split(/(\s+|[.,;()\-+*\/:"'!?<>])/);
|
||
|
||
return parts.map(part => {
|
||
if (!part.trim()) return part;
|
||
const np = norm(part);
|
||
const sp = stem(part);
|
||
const isMatch = tokens.some((tok, idx) => wordMatches(part, tok, stems[idx]));
|
||
if (isMatch) {
|
||
return `<mark class="match-highlight">${part}</mark>`;
|
||
}
|
||
return part;
|
||
}).join("");
|
||
}
|
||
|
||
// Robustní vyhledávání v sešitu Kamily Pešek
|
||
function searchKniha(query, categoryFilter = 'Vše') {
|
||
if (!query || !query.trim()) return [];
|
||
const tokens = tokenize(query);
|
||
if (tokens.length === 0) return [];
|
||
const stems = tokens.map(stem);
|
||
|
||
const source = categoryFilter === 'Vše'
|
||
? KNIHA_RECEPTU
|
||
: KNIHA_RECEPTU.filter(r => r.kategorie === categoryFilter);
|
||
|
||
const results = [];
|
||
|
||
source.forEach(r => {
|
||
const titleWords = tokenize(r.nazev);
|
||
const surWords = tokenize((r.suroviny || []).join(" "));
|
||
const posWords = tokenize(r.postup || "");
|
||
const katWords = tokenize(r.kategorie || "");
|
||
|
||
let score = 0;
|
||
let matchedTokens = 0;
|
||
let snippet = "";
|
||
|
||
tokens.forEach((tok, idx) => {
|
||
const st = stems[idx];
|
||
let hit = false;
|
||
|
||
// 1. Shoda v názvu (priorita #1)
|
||
if (titleWords.some(tw => wordMatches(tw, tok, st))) {
|
||
score += 150;
|
||
hit = true;
|
||
}
|
||
|
||
// 2. Shoda v surovinách
|
||
if (surWords.some(sw => wordMatches(sw, tok, st))) {
|
||
score += 50;
|
||
hit = true;
|
||
if (!snippet) {
|
||
const found = (r.suroviny || []).find(s => tokenize(s).some(sw => wordMatches(sw, tok, st)));
|
||
if (found) snippet = "Surovina: " + found;
|
||
}
|
||
}
|
||
|
||
// 3. Shoda v kategorii
|
||
if (katWords.some(kw => wordMatches(kw, tok, st))) {
|
||
score += 25;
|
||
hit = true;
|
||
}
|
||
|
||
// 4. Shoda v postupu
|
||
if (posWords.some(pw => wordMatches(pw, tok, st))) {
|
||
score += 20;
|
||
hit = true;
|
||
if (!snippet && r.postup) {
|
||
snippet = "Postup: " + r.postup.substring(0, 110) + "...";
|
||
}
|
||
}
|
||
|
||
if (hit) matchedTokens++;
|
||
});
|
||
|
||
// Masivní bonus za pokrytí všech hledaných slov (AND logika)
|
||
if (matchedTokens === tokens.length) {
|
||
score += 500 * tokens.length;
|
||
}
|
||
|
||
if (score > 0) {
|
||
results.push({
|
||
recipe: r,
|
||
score: score,
|
||
matchedCount: matchedTokens,
|
||
snippet: snippet
|
||
});
|
||
}
|
||
});
|
||
|
||
// Seřadit: nejdřív ty, co splňují všechny tokeny, pak podle skóre
|
||
results.sort((a, b) => {
|
||
const fullA = a.matchedCount === tokens.length ? 1 : 0;
|
||
const fullB = b.matchedCount === tokens.length ? 1 : 0;
|
||
if (fullA !== fullB) return fullB - fullA;
|
||
return b.score - a.score || a.recipe.id - b.recipe.id;
|
||
});
|
||
|
||
return results;
|
||
}
|
||
|
||
// Categories list
|
||
const KATEGORIE = [
|
||
'Vše',
|
||
'Polévky',
|
||
'Masitá jídla',
|
||
'Vánoční cukroví',
|
||
'Moučníky a sladké',
|
||
'Přílohy a bezmasá',
|
||
'Omáčky',
|
||
'Saláty a zelenina',
|
||
'Ostatní'
|
||
];
|
||
|
||
// =========================================================================
|
||
// VIEWS & CONTROLLERS
|
||
// =========================================================================
|
||
|
||
// 1. HLAVNÍ NABÍDKA
|
||
function renderMainMenu() {
|
||
currentView = 'menu';
|
||
winTitleEl.textContent = "KUCHAŘKA KAMILY PEŠEK // HLAVNÍ NABÍDKA PASCAL 7.0";
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
KUCHAŘKA KAMILY PEŠEK // AUTENTICKÝ SEŠIT RECEPTŮ (1998–2026)
|
||
Software-salon ALTÁN (Ing. Helena Heroutová, Dejvice) & Konzdat
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
|
||
<div class="dos-box">
|
||
<span style="color:var(--dos-yellow)">Vítejte v rodinném gastronomickém archivu babičky <strong>Kamily Pešek</strong>.</span>
|
||
Tento systém obsahuje <strong>${KNIHA_RECEPTU.length} přepsaných receptů</strong> z jejího ručně psaného
|
||
sešitu (strany 1–43) a <strong>${HISTORICKA_JIDLA.length} záznamů rodinného jídelníčku</strong> z let 1998–2021.
|
||
</div>
|
||
|
||
<div class="dos-header" style="color:var(--dos-cyan)">── RYCHLÉ VYHLEDÁVÁNÍ V KUCHAŘCE ───────────────────────────────────────────</div>
|
||
<div class="dos-input-group" style="margin:6px 0 12px 0">
|
||
<span style="color:var(--dos-yellow); font-weight:bold">HLEDAT:</span>
|
||
<input type="text" id="menu-quick-search" class="dos-input" placeholder="Napište název, surovinu či postup (např. kuře, bábovka, bramborový salát, česnek)...">
|
||
<button class="dos-btn" onclick="triggerMenuSearch()">HLEDAT [ENTER]</button>
|
||
</div>
|
||
|
||
<div class="dos-header" style="color:var(--dos-cyan)">── HLAVNÍ PROGRAMOVÉ MODULY ────────────────────────────────────────────────</div>
|
||
<div class="dos-menu-list">
|
||
<div class="dos-menu-item" onclick="openSearch()">
|
||
<span class="hotkey">[H / /]</span>
|
||
<span style="color:var(--dos-yellow); font-weight:bold">>> FULLTEXTOVÉ HLEDÁNÍ V CELÉM SEŠITU (${KNIHA_RECEPTU.length} receptů) <<</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openReceptyList('Vše')">
|
||
<span class="hotkey">[R]</span>
|
||
<span style="color:var(--dos-white); font-weight:bold">Procházet receptář Kamily Pešek podle kategorií</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openSuroviny()">
|
||
<span class="hotkey">[S]</span>
|
||
<span style="color:var(--dos-green); font-weight:bold">Vařit podle surovin ve špajzu a lednici (Algoritmus KUCHARKA.EXE)</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openRandomRecipe()">
|
||
<span class="hotkey">[N]</span>
|
||
<span>Náhodný recept pro dnešní den (Inspirace ze sešitu)</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openDavno()">
|
||
<span class="hotkey">[D]</span>
|
||
<span>Doporučení: Dávno nevařeno (Prevence stereotypu - DoporucD)</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openOblibena()">
|
||
<span class="hotkey">[O]</span>
|
||
<span>Železná statistika oblíbených jídel rodinného stolu (DoporucO)</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openKronika()">
|
||
<span class="hotkey">[K]</span>
|
||
<span>Kronika rodinného stolu (${HISTORICKA_JIDLA.length} záznamů Bohnice a Dejvice)</span>
|
||
</div>
|
||
<div class="dos-menu-item" onclick="openAltan()">
|
||
<span class="hotkey">[A]</span>
|
||
<span>O programu & Software-salon ALTÁN (Historie Dejvic & Bohnic)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dos-header" style="color:var(--dos-cyan)">── RYCHLÝ VÝBĚR DLE KATEGORIE ──────────────────────────────────────────────</div>
|
||
<div style="display:flex; flex-wrap:wrap; gap:8px; margin:8px 0">
|
||
`;
|
||
|
||
KATEGORIE.slice(1).forEach((cat, idx) => {
|
||
const count = KNIHA_RECEPTU.filter(r => r.kategorie === cat).length;
|
||
html += `<button class="dos-btn" onclick="openReceptyList('${cat}')">[${idx + 1}] ${cat} (${count})</button>`;
|
||
});
|
||
|
||
html += `</div>
|
||
|
||
<div style="margin-top:14px; color:var(--dos-grey)">
|
||
Zvolte klávesu <span class="hotkey">[H, R, S, N, D, O, K, A, /]</span> nebo kategorii <span class="hotkey">[1-8]</span>. Na mobilu klepněte prstem.
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
|
||
const quickInput = document.getElementById('menu-quick-search');
|
||
if (quickInput) {
|
||
quickInput.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') triggerMenuSearch();
|
||
});
|
||
}
|
||
}
|
||
|
||
function triggerMenuSearch() {
|
||
const input = document.getElementById('menu-quick-search');
|
||
if (input && input.value.trim()) {
|
||
openSearch(input.value.trim());
|
||
} else {
|
||
openSearch();
|
||
}
|
||
}
|
||
|
||
// 2. FULLTEXTOVÉ VYHLEDÁVÁNÍ (DEDIKOVANÉ ROZHRANÍ)
|
||
function openSearch(defaultQuery = "") {
|
||
currentView = 'hledat';
|
||
playSpeaker(750, 0.05);
|
||
winTitleEl.textContent = `HLEDÁNÍ V KUCHAŘCE KAMILY PEŠEK // FULLTEXT ENGINE`;
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
FULLTEXTOVÉ VYHLEDÁVÁNÍ // RECEPTÁŘ KAMILY PEŠEK (${KNIHA_RECEPTU.length} RECEPTŮ)
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
|
||
<div class="dos-box">
|
||
Zadejte hledaný název jídla, surovinu nebo kousek postupu (např. <em>kuře, bábovka, kmínová, guláš, česnek, ořechy, brambory</em>).
|
||
Vyhledávač automaticky ohýbá česká slova a prohledává všech <strong>${KNIHA_RECEPTU.length} receptů ze sešitu</strong>.
|
||
</div>
|
||
|
||
<div class="dos-input-group" style="margin:10px 0 12px 0">
|
||
<span style="color:var(--dos-yellow); font-weight:bold">HLEDAT V RECEPTÁŘI:</span>
|
||
<input type="text" id="global-search-input" class="dos-input" placeholder="Zadejte hledané slovo..." value="${defaultQuery}">
|
||
<button class="dos-btn" onclick="executeGlobalSearch()">HLEDAT [ENTER]</button>
|
||
</div>
|
||
|
||
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom:12px">
|
||
<span style="color:var(--dos-cyan); align-self:center; font-size:13px">Časté dotazy:</span>
|
||
<button class="dos-btn" onclick="quickQuery('kuře')">kuře</button>
|
||
<button class="dos-btn" onclick="quickQuery('brambory')">brambory</button>
|
||
<button class="dos-btn" onclick="quickQuery('bábovka')">bábovka</button>
|
||
<button class="dos-btn" onclick="quickQuery('polévka')">polévka</button>
|
||
<button class="dos-btn" onclick="quickQuery('guláš')">guláš</button>
|
||
<button class="dos-btn" onclick="quickQuery('salát')">salát</button>
|
||
<button class="dos-btn" onclick="quickQuery('cukroví')">cukroví</button>
|
||
<button class="dos-btn" onclick="quickQuery('česnek')">česnek</button>
|
||
<button class="dos-btn" onclick="quickQuery('tvaroh')">tvaroh</button>
|
||
</div>
|
||
|
||
<div id="global-search-results">
|
||
<!-- Dynamické výsledky hledání -->
|
||
</div>
|
||
|
||
<div style="margin-top:20px; display:flex; gap:10px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO MENU</button>
|
||
<button class="dos-btn" onclick="openReceptyList('Vše')">[R] CELÝ SEZNAM RECEPTŮ</button>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
|
||
const input = document.getElementById('global-search-input');
|
||
input.focus();
|
||
input.setSelectionRange(input.value.length, input.value.length);
|
||
|
||
input.addEventListener('input', () => {
|
||
executeGlobalSearch();
|
||
});
|
||
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') executeGlobalSearch();
|
||
});
|
||
|
||
if (defaultQuery) {
|
||
executeGlobalSearch();
|
||
}
|
||
}
|
||
|
||
function quickQuery(q) {
|
||
const input = document.getElementById('global-search-input');
|
||
if (input) {
|
||
input.value = q;
|
||
executeGlobalSearch();
|
||
input.focus();
|
||
}
|
||
}
|
||
|
||
function executeGlobalSearch() {
|
||
const input = document.getElementById('global-search-input');
|
||
const resEl = document.getElementById('global-search-results');
|
||
if (!input || !resEl) return;
|
||
|
||
const query = input.value.trim();
|
||
if (!query) {
|
||
resEl.innerHTML = `<div style="color:var(--dos-grey); padding:10px">Zadejte alespoň 2 písmena pro vyhledání v sešitu Kamily Pešek...</div>`;
|
||
return;
|
||
}
|
||
|
||
playSpeaker(850, 0.03);
|
||
const results = searchKniha(query, 'Vše');
|
||
|
||
let html = "";
|
||
if (results.length > 0) {
|
||
html += `<div style="color:var(--dos-yellow); margin-bottom:8px; font-weight:bold">
|
||
★ Nalezeno ${results.length} receptů pro dotaz "${query}":
|
||
</div>`;
|
||
html += `<div class="dos-menu-list">`;
|
||
|
||
results.slice(0, 20).forEach(item => {
|
||
const r = item.recipe;
|
||
const highlightedTitle = highlightMatches(r.nazev, query);
|
||
const highlightedSnippet = item.snippet ? highlightMatches(item.snippet, query) : "";
|
||
|
||
html += `
|
||
<div class="dos-menu-item" onclick="openRecipe(${r.id})" style="flex-direction:column; align-items:flex-start">
|
||
<div style="display:flex; width:100%; justify-content:space-between; align-items:center">
|
||
<span>
|
||
<span class="hotkey">[${r.id}]</span>
|
||
<span style="color:var(--dos-white); font-weight:bold">${highlightedTitle}</span>
|
||
</span>
|
||
<span class="tag-badge">Str. ${r.strana} | ${r.kategorie}</span>
|
||
</div>
|
||
${highlightedSnippet ? `<div class="search-result-snippet">${highlightedSnippet}</div>` : ''}
|
||
</div>`;
|
||
});
|
||
html += `</div>`;
|
||
|
||
if (results.length > 20) {
|
||
html += `<div style="color:var(--dos-grey); margin-top:8px; font-size:13px">...a dalších ${results.length - 20} receptů. Upřesněte dotaz pro zúžení výběru.</div>`;
|
||
}
|
||
} else {
|
||
html += `<div style="color:var(--dos-red); padding:12px 0">
|
||
Pro dotaz "${query}" nebyl v sešitu Kamily Pešek nalezen žádný recept.
|
||
</div>`;
|
||
|
||
// Prohledat i kroniku 568 jídel jako zálohu
|
||
const histMatches = HISTORICKA_JIDLA.filter(j => norm(j.jidlo).includes(norm(query)));
|
||
if (histMatches.length > 0) {
|
||
html += `<div style="color:var(--dos-cyan); margin:12px 0 6px 0; font-weight:bold">
|
||
📜 Nalezeno ale v Kronice rodinného stolu (1998–2021):
|
||
</div>`;
|
||
histMatches.slice(0, 8).forEach(j => {
|
||
html += `<div style="padding:3px 8px; border-left:2px solid var(--dos-cyan); margin:3px 0">
|
||
<span style="color:var(--dos-white)">${j.jidlo}</span>
|
||
<span style="color:var(--dos-grey)"> [${j.datum}, ${j.typ}]</span>
|
||
</div>`;
|
||
});
|
||
}
|
||
}
|
||
|
||
resEl.innerHTML = html;
|
||
}
|
||
|
||
// 3. SEZNAM RECEPTŮ PODLE KATEGORIÍ S INTEGROVANÝM VYHLEDÁVÁNÍM
|
||
function openReceptyList(category = 'Vše', page = 0, initialQuery = "") {
|
||
currentView = 'recepty_list';
|
||
currentCategory = category;
|
||
listPage = page;
|
||
playSpeaker(700, 0.04);
|
||
|
||
const baseList = category === 'Vše' ? KNIHA_RECEPTU : KNIHA_RECEPTU.filter(r => r.kategorie === category);
|
||
const totalPages = Math.ceil(baseList.length / PAGE_SIZE);
|
||
const pageItems = baseList.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
|
||
|
||
winTitleEl.textContent = `RECEPTÁŘ KAMILY PEŠEK // ${category.toUpperCase()} (${baseList.length} RECEPTŮ)`;
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
RECEPTÁŘ KAMILY PEŠEK // KATEGORIE: ${category.toUpperCase()} (Strana ${page + 1}/${totalPages || 1})
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
|
||
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom:10px">
|
||
`;
|
||
KATEGORIE.forEach(cat => {
|
||
const activeClass = cat === category ? 'style="background-color:var(--dos-yellow); color:var(--dos-black)"' : '';
|
||
html += `<button class="dos-btn" ${activeClass} onclick="openReceptyList('${cat}', 0)">${cat}</button>`;
|
||
});
|
||
|
||
html += `</div>
|
||
|
||
<div class="dos-input-group" style="margin:6px 0 12px 0">
|
||
<input type="text" id="recipe-search-input" class="dos-input" placeholder="Rychlé hledání v receptech (např. bábovka, kuře, brambory, kmínová)..." value="${initialQuery}">
|
||
<button class="dos-btn" onclick="clearRecipeSearch('${category}')">ZRUŠIT FILTR</button>
|
||
</div>
|
||
|
||
<div id="search-status-bar" style="margin-bottom:8px; font-size:13px; color:var(--dos-yellow)"></div>
|
||
|
||
<div class="dos-menu-list" id="recipe-items-container">
|
||
`;
|
||
|
||
pageItems.forEach((r) => {
|
||
html += `<div class="dos-menu-item" onclick="openRecipe(${r.id})">
|
||
<span class="hotkey">[${r.id}]</span>
|
||
<span style="color:var(--dos-white)">${r.nazev}</span>
|
||
<span class="tag-badge" style="margin-left:auto">Str. ${r.strana} | ${r.kategorie}</span>
|
||
</div>`;
|
||
});
|
||
|
||
html += `</div>
|
||
|
||
<div id="recipe-pagination" style="margin-top:16px; display:flex; justify-content:space-between; align-items:center">
|
||
<div style="display:flex; gap:10px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] HLAVNÍ NABÍDKA</button>
|
||
${page > 0 ? `<button class="dos-btn" onclick="openReceptyList('${category}', ${page - 1})">[◄] PŘEDCHOZÍ STRANA</button>` : ''}
|
||
${page < totalPages - 1 ? `<button class="dos-btn" onclick="openReceptyList('${category}', ${page + 1})">[►] DALŠÍ STRANA</button>` : ''}
|
||
</div>
|
||
<div style="color:var(--dos-yellow); font-size:13px">
|
||
Celkem ${baseList.length} receptů v této kategorii
|
||
</div>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
|
||
// Search input live filtering
|
||
const searchInput = document.getElementById('recipe-search-input');
|
||
const container = document.getElementById('recipe-items-container');
|
||
const pagination = document.getElementById('recipe-pagination');
|
||
const statusBar = document.getElementById('search-status-bar');
|
||
|
||
const handleSearch = () => {
|
||
const q = searchInput.value.trim();
|
||
if (!q) {
|
||
statusBar.innerHTML = "";
|
||
pagination.style.display = "flex";
|
||
let defaultHtml = "";
|
||
pageItems.forEach((r) => {
|
||
defaultHtml += `<div class="dos-menu-item" onclick="openRecipe(${r.id})">
|
||
<span class="hotkey">[${r.id}]</span>
|
||
<span style="color:var(--dos-white)">${r.nazev}</span>
|
||
<span class="tag-badge" style="margin-left:auto">Str. ${r.strana} | ${r.kategorie}</span>
|
||
</div>`;
|
||
});
|
||
container.innerHTML = defaultHtml;
|
||
return;
|
||
}
|
||
|
||
// Hledat primárně v celé knize, pokud uživatel píše dotaz
|
||
const matched = searchKniha(q, 'Vše');
|
||
pagination.style.display = "none";
|
||
statusBar.innerHTML = `Nalezeno <strong>${matched.length} receptů</strong> v celém sešitu pro dotaz "<em>${q}</em>":`;
|
||
|
||
let mHtml = "";
|
||
matched.slice(0, 25).forEach(item => {
|
||
const r = item.recipe;
|
||
const highlightedTitle = highlightMatches(r.nazev, q);
|
||
const snippet = item.snippet ? highlightMatches(item.snippet, q) : "";
|
||
mHtml += `<div class="dos-menu-item" onclick="openRecipe(${r.id})" style="flex-direction:column; align-items:flex-start">
|
||
<div style="display:flex; width:100%; justify-content:space-between; align-items:center">
|
||
<span>
|
||
<span class="hotkey">[${r.id}]</span>
|
||
<span style="color:var(--dos-white); font-weight:bold">${highlightedTitle}</span>
|
||
</span>
|
||
<span class="tag-badge">Str. ${r.strana} | ${r.kategorie}</span>
|
||
</div>
|
||
${snippet ? `<div class="search-result-snippet">${snippet}</div>` : ''}
|
||
</div>`;
|
||
});
|
||
|
||
if (matched.length === 0) {
|
||
mHtml = `<div style="color:var(--dos-red); padding:10px">Žádný recept v sešitu neodpovídá hledanému výrazu "${q}".</div>`;
|
||
}
|
||
container.innerHTML = mHtml;
|
||
};
|
||
|
||
searchInput.addEventListener('input', handleSearch);
|
||
|
||
if (initialQuery) {
|
||
handleSearch();
|
||
}
|
||
}
|
||
|
||
function clearRecipeSearch(category) {
|
||
openReceptyList(category, 0);
|
||
}
|
||
|
||
// 4. DETAIL RECEPTU
|
||
function openRecipe(id) {
|
||
const r = KNIHA_RECEPTU.find(x => x.id === id);
|
||
if (!r) return;
|
||
currentView = 'recept';
|
||
currentRecipeId = id;
|
||
playSpeaker(900, 0.05);
|
||
|
||
winTitleEl.textContent = `RECEPT [${r.id}/${KNIHA_RECEPTU.length}]: ${r.nazev.toUpperCase()}`;
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
RECEPT Č. ${r.id}: ${r.nazev}
|
||
Původní sešit Kamily Pešek: Strana ${r.strana} | Kategorie: ${r.kategorie}
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
|
||
<div class="dos-header" style="color:var(--dos-cyan)">── POTŘEBNÉ SUROVINY ZE SEŠITU ─────────────────────────────────────────────</div>
|
||
<ul style="list-style:none; padding-left:10px; margin-bottom:15px">
|
||
`;
|
||
if (r.suroviny && r.suroviny.length > 0) {
|
||
r.suroviny.forEach(s => {
|
||
html += `<li style="padding:2px 0">• <span style="color:var(--dos-yellow)">${s}</span></li>`;
|
||
});
|
||
} else {
|
||
html += `<li style="padding:2px 0; color:var(--dos-grey)">• Běžné základní suroviny dle chuti a zvyklostí</li>`;
|
||
}
|
||
|
||
html += `</ul>
|
||
|
||
<div class="dos-header" style="color:var(--dos-cyan)">── POSTUP PŘÍPRAVY ZE SEŠITU KAMILY PEŠEK ─────────────────────────────────</div>
|
||
<div class="dos-box" style="line-height:1.55; color:var(--dos-white)">
|
||
${r.postup || "Tradiční rodinný postup podle zápisu Kamily Pešek."}
|
||
</div>
|
||
|
||
<div class="secret-badge">
|
||
<strong>★ TAJEMSTVÍ KAMILY PEŠEK:</strong><br>
|
||
<em>"${r.tajemstvi}"</em>
|
||
</div>
|
||
|
||
<div style="margin-top:20px; display:flex; gap:10px; flex-wrap:wrap">
|
||
<button class="dos-btn" onclick="openReceptyList(currentCategory, listPage)">[ESC] ZPĚT DO SEZNAMU</button>
|
||
<button class="dos-btn" onclick="openSearch()">[/] HLEDAT JINÝ</button>
|
||
<button class="dos-btn" onclick="renderMainMenu()">[H] HLAVNÍ NABÍDKA</button>
|
||
${id > 1 ? `<button class="dos-btn" onclick="openRecipe(${id - 1})">[◄] PŘEDCHOZÍ (${id - 1})</button>` : ''}
|
||
${id < KNIHA_RECEPTU.length ? `<button class="dos-btn" onclick="openRecipe(${id + 1})">[►] DALŠÍ (${id + 1})</button>` : ''}
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
}
|
||
|
||
// 5. NÁHODNÝ RECEPT
|
||
function openRandomRecipe() {
|
||
if (KNIHA_RECEPTU.length === 0) return;
|
||
const randomId = Math.floor(Math.random() * KNIHA_RECEPTU.length) + 1;
|
||
openRecipe(randomId);
|
||
}
|
||
|
||
// 6. VAŘENÍ ZE SUROVIN VE ŠPAJZU (ALGORITMUS)
|
||
function openSuroviny(defaultQuery = "") {
|
||
currentView = 'suroviny';
|
||
playSpeaker(700, 0.06);
|
||
winTitleEl.textContent = "ALGORITMUS: VAŘENÍ PODLE SUROVIN VE ŠPAJZU A LEDNICI";
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
VAŘENÍ PODLE SUROVIN // ROZHODOVACÍ SYSTÉM PRO HOSPODYNI (KUCHARKA.EXE)
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
<div class="dos-box">
|
||
Zadejte suroviny, které máte právě doma ve špajzu a lednici (oddělené čárkou či mezerou).
|
||
Algoritmus prohledá <strong>všech ${KNIHA_RECEPTU.length} receptů Kamily Pešek</strong> i <strong>${HISTORICKA_JIDLA.length} jídel z kroniky</strong>.
|
||
</div>
|
||
|
||
<div class="dos-input-group">
|
||
<span style="color:var(--dos-yellow); font-weight:bold">MÁM DOMA:</span>
|
||
<input type="text" id="suroviny-input" class="dos-input" placeholder="např. brambory, maso, smetana, vejce, kuře, jablka..." value="${defaultQuery}">
|
||
<button class="dos-btn" onclick="doSearchSuroviny()">HLEDAT [ENTER]</button>
|
||
</div>
|
||
|
||
<div style="display:flex; gap:6px; flex-wrap:wrap; margin:10px 0">
|
||
<span style="color:var(--dos-cyan); align-self:center; font-size:13px">Rychlé koše:</span>
|
||
<button class="dos-btn" onclick="fillPantry('kuře, máslo, česnek')">Kuře + česnek</button>
|
||
<button class="dos-btn" onclick="fillPantry('brambory, cibule, vejce')">Brambory + vejce</button>
|
||
<button class="dos-btn" onclick="fillPantry('tvaroh, mouka, cukr')">Tvaroh + mouka</button>
|
||
<button class="dos-btn" onclick="fillPantry('jablka, skořice, máslo')">Jablka + skořice</button>
|
||
<button class="dos-btn" onclick="fillPantry('mleté maso, cibule, česnek')">Mleté maso</button>
|
||
</div>
|
||
|
||
<div id="vysledky-hledani">
|
||
<!-- Dynamic match results -->
|
||
</div>
|
||
|
||
<div style="margin-top:20px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO HLAVNÍHO MENU</button>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
|
||
const input = document.getElementById('suroviny-input');
|
||
input.focus();
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') doSearchSuroviny();
|
||
});
|
||
|
||
if (defaultQuery) {
|
||
doSearchSuroviny();
|
||
}
|
||
}
|
||
|
||
function fillPantry(str) {
|
||
const input = document.getElementById('suroviny-input');
|
||
if (input) {
|
||
input.value = str;
|
||
doSearchSuroviny();
|
||
}
|
||
}
|
||
|
||
function doSearchSuroviny() {
|
||
const query = document.getElementById('suroviny-input').value.trim();
|
||
const resEl = document.getElementById('vysledky-hledani');
|
||
if (!query) {
|
||
resEl.innerHTML = `<div style="color:var(--dos-red)">Zadejte alespoň jednu surovinu!</div>`;
|
||
return;
|
||
}
|
||
playSpeaker(1000, 0.04);
|
||
|
||
const tokens = tokenize(query);
|
||
const stems = tokens.map(stem);
|
||
|
||
// Score Recipes (suroviny, nazev, postup)
|
||
const scoredRecipes = KNIHA_RECEPTU.map(r => {
|
||
let matches = [];
|
||
const normFull = norm(r.nazev + " " + (r.suroviny || []).join(" ") + " " + (r.postup || ""));
|
||
|
||
tokens.forEach((tok, idx) => {
|
||
const st = stems[idx];
|
||
if (normFull.includes(tok) || (st.length >= 3 && normFull.includes(st))) {
|
||
matches.push(tok);
|
||
}
|
||
});
|
||
matches = [...new Set(matches)];
|
||
return { recipe: r, score: matches.length, matchedTokens: matches };
|
||
}).filter(x => x.score > 0).sort((a, b) => b.score - a.score || a.recipe.id - b.recipe.id);
|
||
|
||
// Score Historical Database
|
||
const scoredHist = HISTORICKA_JIDLA.map(j => {
|
||
let matches = [];
|
||
const nJidlo = norm(j.jidlo);
|
||
tokens.forEach((tok, idx) => {
|
||
const st = stems[idx];
|
||
if (nJidlo.includes(tok) || (st.length >= 3 && nJidlo.includes(st))) {
|
||
matches.push(tok);
|
||
}
|
||
});
|
||
matches = [...new Set(matches)];
|
||
return { jidlo: j, score: matches.length, matchedTokens: matches };
|
||
}).filter(x => x.score > 0).sort((a, b) => b.score - a.score);
|
||
|
||
let html = `<div class="dos-header" style="color:var(--dos-green)">VÝSLEDEK SHODY PRO: "${query}" (Nalezeno ${scoredRecipes.length} receptů Kamily Pešek)</div>`;
|
||
|
||
if (scoredRecipes.length > 0) {
|
||
html += `<div style="color:var(--dos-yellow); margin:10px 0; font-weight:bold">★ NALEZENÉ RECEPTY ZE SEŠITU KAMILY PEŠEK:</div>`;
|
||
scoredRecipes.slice(0, 15).forEach(item => {
|
||
html += `<div class="dos-menu-item" onclick="openRecipe(${item.recipe.id})">
|
||
<span class="hotkey">[${item.recipe.id}]</span>
|
||
<span style="color:var(--dos-white)">${item.recipe.nazev}</span>
|
||
<span style="color:var(--dos-green); margin-left:auto">Shoda: ${item.score} surovin (${item.matchedTokens.join(', ')})</span>
|
||
</div>`;
|
||
});
|
||
}
|
||
|
||
if (scoredHist.length > 0) {
|
||
html += `<div style="color:var(--dos-cyan); margin:15px 0 5px 0; font-weight:bold">📜 ZÁZNAMY Z HISTORICKÉHO JÍDELNÍČKU (1998–2021):</div>`;
|
||
scoredHist.slice(0, 8).forEach(item => {
|
||
html += `<div style="padding:3px 8px; border-left:2px solid var(--dos-cyan); margin:4px 0">
|
||
<span style="color:var(--dos-white)">${item.jidlo.jidlo}</span>
|
||
<span style="color:var(--dos-grey)"> [${item.jidlo.datum}, ${item.jidlo.typ}]</span>
|
||
<span style="color:var(--dos-yellow); float:right">shoda: ${item.matchedTokens.join(', ')}</span>
|
||
</div>`;
|
||
});
|
||
}
|
||
|
||
if (scoredRecipes.length === 0 && scoredHist.length === 0) {
|
||
html += `<div style="color:var(--dos-red); padding:10px">Pro tyto suroviny nebyla v databázi nalezena přímá shoda. Zkuste zadat základní ingredience jako: brambory, maso, vejce, smetana, kuře.</div>`;
|
||
}
|
||
|
||
resEl.innerHTML = html;
|
||
}
|
||
|
||
// 7. DÁVNO NEVAŘENO
|
||
function openDavno() {
|
||
currentView = 'davno';
|
||
playSpeaker(600, 0.05);
|
||
winTitleEl.textContent = "DOPORUČENÍ: DÁVNO NEVAŘENO // PREVENCE STEREOTYPU";
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
DOPORUČENÍ: DÁVNO NEVAŘENO // HISTORICKÝ CYKLUS RODINY (DoporucD)
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
<div class="dos-box">
|
||
Tento modul dekompilovaný z původního Delphi programu prochází archiv datumu
|
||
a vyhledává pokrmy, které se na stole neobjevily nejdéle, aby se jídelníček netočil.
|
||
</div>
|
||
|
||
<div class="dos-header" style="color:var(--dos-yellow)">DOPORUČENÁ JÍDLA S NEJDELŠÍM ČASOVÝM ODSTUPEM:</div>
|
||
<div class="dos-menu-list">
|
||
`;
|
||
HISTORICKA_JIDLA.slice(0, 12).forEach((j, idx) => {
|
||
html += `<div style="padding:5px 10px; border-bottom:1px dashed var(--dos-cyan); display:flex; justify-content:space-between">
|
||
<span><strong style="color:var(--dos-cyan)">${idx + 1}.</strong> <span style="color:var(--dos-white)">${j.jidlo}</span></span>
|
||
<span><span style="color:var(--dos-yellow)">Naposledy: ${j.datum}</span> | ${j.typ}</span>
|
||
</div>`;
|
||
});
|
||
|
||
html += `</div>
|
||
<div style="margin-top:20px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO MENU</button>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
}
|
||
|
||
// 8. OBLÍBENÁ JÍDLA
|
||
function openOblibena() {
|
||
currentView = 'oblibena';
|
||
playSpeaker(850, 0.05);
|
||
winTitleEl.textContent = "OBLÍBENÁ JÍDLA // ŽELEZNÁ STATISTIKA STOLU";
|
||
|
||
const freq = {};
|
||
HISTORICKA_JIDLA.forEach(j => {
|
||
const title = j.jidlo.split(',')[0].trim();
|
||
freq[title] = (freq[title] || 0) + 1;
|
||
});
|
||
|
||
const top = Object.entries(freq).sort((a, b) => b[1] - a[1]).slice(0, 12);
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
ŽELEZNÁ STATISTIKA OBLÍBENÝCH JÍDEL RODINNÉHO STOLU (DoporucO)
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
<div class="dos-box">
|
||
Frekvenční žebříček z ${HISTORICKA_JIDLA.length} záznamů rodinného jídelníčku z let 1998–2021:
|
||
</div>
|
||
|
||
<div class="dos-menu-list">
|
||
`;
|
||
const maxVal = top[0] ? top[0][1] : 1;
|
||
top.forEach(([name, count], idx) => {
|
||
const barWidth = Math.max(2, Math.round((count / maxVal) * 24));
|
||
const bar = "█".repeat(barWidth);
|
||
html += `<div style="padding:4px 8px; font-family:monospace">
|
||
<span style="color:var(--dos-yellow); width:24px; display:inline-block">${idx + 1}.</span>
|
||
<span style="color:var(--dos-white); display:inline-block; width:300px">${name}</span>
|
||
<span style="color:var(--dos-cyan)">${bar}</span>
|
||
<span style="color:var(--dos-yellow); margin-left:10px">${count}×</span>
|
||
</div>`;
|
||
});
|
||
|
||
html += `</div>
|
||
<div style="margin-top:20px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO MENU</button>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
}
|
||
|
||
// 9. KRONIKA RODINNÉHO STOLU
|
||
function openKronika(page = 0) {
|
||
currentView = 'kronika';
|
||
playSpeaker(700, 0.04);
|
||
winTitleEl.textContent = `KRONIKA STOLU BOHNICE & DEJVICE (${HISTORICKA_JIDLA.length} ZÁZNAMŮ)`;
|
||
|
||
const totalPages = Math.ceil(HISTORICKA_JIDLA.length / 20);
|
||
const items = HISTORICKA_JIDLA.slice(page * 20, (page + 1) * 20);
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
KRONIKA RODINNÉHO STOLU // BOHNICE & DEJVICE (Strana ${page + 1}/${totalPages})
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
|
||
<div class="dos-box">
|
||
Kompletní záznamy o tom, co se skutečně vařilo a jedlo v rodině Kamily Pešek v letech 1998–2021.
|
||
</div>
|
||
|
||
<div class="dos-menu-list">
|
||
`;
|
||
items.forEach((j) => {
|
||
html += `<div style="padding:4px 8px; border-bottom:1px solid #002244; display:flex; justify-content:space-between">
|
||
<span><strong style="color:var(--dos-yellow)">${j.datum}:</strong> <span style="color:var(--dos-white)">${j.jidlo}</span></span>
|
||
<span class="tag-badge">${j.typ}</span>
|
||
</div>`;
|
||
});
|
||
|
||
html += `</div>
|
||
|
||
<div style="margin-top:16px; display:flex; justify-content:space-between">
|
||
<div style="display:flex; gap:10px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] MENU</button>
|
||
${page > 0 ? `<button class="dos-btn" onclick="openKronika(${page - 1})">[◄] PŘEDCHOZÍ</button>` : ''}
|
||
${page < totalPages - 1 ? `<button class="dos-btn" onclick="openKronika(${page + 1})">[►] DALŠÍ</button>` : ''}
|
||
</div>
|
||
<div style="color:var(--dos-yellow); font-size:13px">
|
||
Záznam ${page * 20 + 1}–${Math.min((page + 1) * 20, HISTORICKA_JIDLA.length)} z ${HISTORICKA_JIDLA.length}
|
||
</div>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
}
|
||
|
||
// 10. ALTÁN / NÁPOVĚDA
|
||
function openAltan() {
|
||
currentView = 'altan';
|
||
playSpeaker(750, 0.05);
|
||
winTitleEl.textContent = "SOFTWARE-SALON ALTÁN // DEJVICE & BOHNICE";
|
||
|
||
let html = `
|
||
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
|
||
SOFTWARE-SALON ALTÁN // VÁCLAVKOVA 2, PRAHA 6 (DEJVICE)
|
||
════════════════════════════════════════════════════════════════════════════════</div>
|
||
<div class="dos-box" style="line-height:1.6">
|
||
<span style="color:var(--dos-yellow); font-weight:bold">PŮVODNÍ PROGRAMÁTORSKÁ STOPA Z JEDNOTKY TURBO PASCALU (POMUCKY.BAK):</span><br><br>
|
||
<code>
|
||
const rec : array[1..3] of string = (<br>
|
||
'S PŘIPOMÍNKAMI A DOTAZY SE OBRACEJTE NA ADRESU:',<br>
|
||
' Ing. B. Mikulecký, CSc., Ing. H. Heroutová',<br>
|
||
' Software-salon ALTÁN ',<br>
|
||
' Václavkova 2, 160 00 Praha 6, tel. 02/311 13 25');
|
||
</code>
|
||
<br><br>
|
||
Ing. Helena Heroutová patřila k průkopnické generaci českých softwarových inženýrek,
|
||
které v 80. a 90. letech tvořily základní procedurální knihovny pro Turbo Pascal
|
||
a DOS v dejvickém ateliéru na Václavkově ulici.<br><br>
|
||
Když se na přelomu tisíciletí objevilo vývojové prostředí Borland Delphi, koncept
|
||
rodinné kuchařky se přetavil do plnohodnotné 32bitové aplikace KUCHARKA.EXE.<br><br>
|
||
Tato webová aplikace věrně propojuje kulinářské dědictví babičky <strong>Kamily Pešek</strong>
|
||
(kompletní rukopisný sešit čítající <strong>${KNIHA_RECEPTU.length} receptů</strong>)
|
||
s procedurální logikou Turbo Pascalu pro budoucí generace.
|
||
</div>
|
||
|
||
<div style="margin-top:20px">
|
||
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO MENU</button>
|
||
</div>
|
||
`;
|
||
screenEl.innerHTML = html;
|
||
}
|
||
|
||
// =========================================================================
|
||
// GLOBAL KEYBOARD & TOUCH NAVIGATION
|
||
// =========================================================================
|
||
|
||
window.addEventListener('keydown', (e) => {
|
||
// If typing in input, only ESC blurs
|
||
if (document.activeElement && document.activeElement.tagName === 'INPUT') {
|
||
if (e.key === 'Escape') {
|
||
document.activeElement.blur();
|
||
renderMainMenu();
|
||
}
|
||
return;
|
||
}
|
||
|
||
playSpeaker(800, 0.03);
|
||
|
||
switch (e.key) {
|
||
case '/':
|
||
case 'h': case 'H':
|
||
e.preventDefault();
|
||
openSearch();
|
||
break;
|
||
case 'r': case 'R': case 'F2':
|
||
e.preventDefault();
|
||
openReceptyList('Vše');
|
||
break;
|
||
case 's': case 'S': case 'F3':
|
||
e.preventDefault();
|
||
openSuroviny();
|
||
break;
|
||
case 'n': case 'N':
|
||
openRandomRecipe();
|
||
break;
|
||
case 'd': case 'D': case 'F4':
|
||
e.preventDefault();
|
||
openDavno();
|
||
break;
|
||
case 'o': case 'O':
|
||
openOblibena();
|
||
break;
|
||
case 'k': case 'K': case 'F5':
|
||
e.preventDefault();
|
||
openKronika();
|
||
break;
|
||
case 'a': case 'A': case 'F1':
|
||
e.preventDefault();
|
||
openAltan();
|
||
break;
|
||
case 'Escape':
|
||
renderMainMenu();
|
||
break;
|
||
case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8':
|
||
if (currentView === 'menu') {
|
||
const idx = parseInt(e.key);
|
||
if (KATEGORIE[idx]) openReceptyList(KATEGORIE[idx]);
|
||
}
|
||
break;
|
||
}
|
||
});
|
||
|
||
// Touch / Navigation controls handler
|
||
document.querySelectorAll('[data-act]').forEach(el => {
|
||
el.addEventListener('click', () => {
|
||
const act = el.getAttribute('data-act');
|
||
if (act === 'menu') renderMainMenu();
|
||
else if (act === 'hledat') openSearch();
|
||
else if (act === 'recepty') openReceptyList('Vše');
|
||
else if (act === 'suroviny') openSuroviny();
|
||
else if (act === 'davno') openDavno();
|
||
else if (act === 'oblibena') openOblibena();
|
||
else if (act === 'kronika') openKronika();
|
||
else if (act === 'altan') openAltan();
|
||
});
|
||
});
|
||
|
||
// Sound toggle
|
||
document.getElementById('sound-btn').addEventListener('click', () => {
|
||
soundEnabled = !soundEnabled;
|
||
document.getElementById('sound-btn').textContent = soundEnabled ? '🔊 ZVUK: ON' : '🔈 ZVUK: OFF';
|
||
if (soundEnabled) playSpeaker(1200, 0.08);
|
||
});
|
||
|
||
// Initial boot
|
||
renderMainMenu();
|