Files
kucharka/app.js
T

607 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =========================================================================
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
const KNIHA_RECEPTU = window.KNIHA_RECEPTU || [];
const HISTORICKA_JIDLA = window.HISTORICKA_JIDLA || [];
// App State
let currentView = 'menu'; // 'menu', 'recept', 'recepty_list', '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);
// Normalization function (strip accents & non-alphanumeric)
function norm(str) {
return (str || "").toLowerCase()
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]/g, "");
}
// 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'
];
// Views renderers
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)">── HLAVNÍ PROGRAMOVÉ MODULY ────────────────────────────────────────────────</div>
<div class="dos-menu-list">
<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 CELÝ RECEPTÁŘ KAMILY PEŠEK (${KNIHA_RECEPTU.length} receptů dle kategorií) <<</span>
</div>
<div class="dos-menu-item" onclick="openSuroviny()">
<span class="hotkey">[S]</span>
<span style="color:var(--dos-yellow); font-weight:bold">>> VAŘIT PODLE SUROVIN VE ŠPAJZU << (Algoritmus prohledání lednice)</span>
</div>
<div class="dos-menu-item" onclick="openRandomRecipe()">
<span class="hotkey">[N]</span>
<span style="color:var(--dos-green)">Náhodný recept pro dnešní den (Inspirace z Kamilina 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 (Prohlížeč ${HISTORICKA_JIDLA.length} záznamů z Bohnic a Dejvic)</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 KATEGORIE ──────────────────────────────────────────────────</div>
<div style="display:flex; flex-wrap:wrap; gap:8px; margin:10px 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:20px; color:var(--dos-grey)">
Zvolte klávesu <span class="hotkey">[R, S, N, D, O, K, A]</span> nebo kategorii <span class="hotkey">[1-7]</span>. Na mobilu klepněte prstem.
</div>
`;
screenEl.innerHTML = html;
}
// List of recipes with category filter & pagination
function openReceptyList(category = 'Vše', page = 0) {
currentView = 'recepty_list';
currentCategory = category;
listPage = page;
playSpeaker(700, 0.04);
const filtered = category === 'Vše' ? KNIHA_RECEPTU : KNIHA_RECEPTU.filter(r => r.kategorie === category);
const totalPages = Math.ceil(filtered.length / PAGE_SIZE);
const pageItems = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
winTitleEl.textContent = `RECEPTÁŘ KAMILY PEŠEK // ${category.toUpperCase()} (${filtered.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:12px">
`;
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:8px 0 14px 0">
<input type="text" id="recipe-search-input" class="dos-input" placeholder="Rychlé hledání v receptech (např. bábovka, kuře, kmínová, závin)...">
</div>
<div class="dos-menu-list" id="recipe-items-container">
`;
pageItems.forEach((r, idx) => {
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 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 ${filtered.length} receptů
</div>
</div>
`;
screenEl.innerHTML = html;
// Search input live filtering
const searchInput = document.getElementById('recipe-search-input');
searchInput.addEventListener('input', (e) => {
const q = norm(e.target.value);
if (!q) {
openReceptyList(category, page);
return;
}
const matched = filtered.filter(r => norm(r.nazev).includes(q) || norm(r.postup).includes(q) || r.suroviny.some(s => norm(s).includes(q)));
const container = document.getElementById('recipe-items-container');
let mHtml = "";
matched.slice(0, 15).forEach(r => {
mHtml += `<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>`;
});
if (matched.length === 0) {
mHtml = `<div style="color:var(--dos-red); padding:10px">Žádný recept neodpovídá hledanému výrazu "${e.target.value}".</div>`;
}
container.innerHTML = mHtml;
});
}
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">
`;
r.suroviny.forEach(s => {
html += `<li style="padding:2px 0">• <span style="color:var(--dos-yellow)">${s}</span></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}
</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="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;
}
function openRandomRecipe() {
const randomId = Math.floor(Math.random() * KNIHA_RECEPTU.length) + 1;
openRecipe(randomId);
}
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 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 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 = query.toLowerCase().split(/[\s,]+/).filter(t => t.length > 1).map(norm);
// Score Recipes (both suroviny and postup)
const scoredRecipes = KNIHA_RECEPTU.map(r => {
let matches = [];
const fullText = (r.suroviny.join(' ') + ' ' + r.postup).toLowerCase();
const nFull = norm(fullText);
tokens.forEach(tok => {
if (nFull.includes(tok)) {
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);
// Score Historical Database
const scoredHist = HISTORICKA_JIDLA.map(j => {
let matches = [];
const nJidlo = norm(j.jidlo);
tokens.forEach(tok => {
if (nJidlo.includes(tok)) {
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, 10).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;
}
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;
}
function openOblibena() {
currentView = 'oblibena';
playSpeaker(850, 0.05);
winTitleEl.textContent = "OBLÍBENÁ JÍDLA // ŽELEZNÁ STATISTIKA STOLU";
// Count occurrences of keywords in historical records
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;
}
function openKronika(page = 0) {
currentView = 'kronika';
playSpeaker(650, 0.05);
winTitleEl.textContent = `KRONIKA RODINNÉHO STOLU // ${HISTORICKA_JIDLA.length} ZÁZNAMŮ BOHNICE (1998–2021)`;
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">
Celkem ${HISTORICKA_JIDLA.length} autentických záznamů z databázových souborů JIDLA.TXT a JIDLA-bohnice.TXT.
Od srpna 1998, přes dětská léta až po rok 2021.
</div>
<div style="max-height:360px; overflow-y:auto; border:1px solid var(--dos-cyan); padding:8px">
`;
items.forEach(j => {
html += `<div style="padding:3px 0; border-bottom:1px dotted rgba(0,170,170,0.3)">
<span style="color:var(--dos-yellow); font-weight:bold; width:95px; display:inline-block">${j.datum}</span>
<span class="tag-badge">[${j.typ}]</span>
<span style="color:var(--dos-white); margin-left:6px">${j.jidlo}</span>
</div>`;
});
html += `</div>
<div style="margin-top:16px; display:flex; gap:10px">
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO MENU</button>
${page > 0 ? `<button class="dos-btn" onclick="openKronika(${page - 1})">[◄] PŘEDCHOZÍ (20)</button>` : ''}
${page < totalPages - 1 ? `<button class="dos-btn" onclick="openKronika(${page + 1})">[►] DALŠÍ (20)</button>` : ''}
</div>
`;
screenEl.innerHTML = html;
}
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>
&nbsp;&nbsp;'S PŘIPOMÍNKAMI A DOTAZY SE OBRACEJTE NA ADRESU:',<br>
&nbsp;&nbsp;' Ing. B. Mikulecký, CSc., Ing. H. Heroutová',<br>
&nbsp;&nbsp;' Software-salon ALTÁN ',<br>
&nbsp;&nbsp;' 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 Handler (Turbo Pascal Hotkeys)
window.addEventListener('keydown', (e) => {
// If in input, only hijack ESC
if (document.activeElement && document.activeElement.tagName === 'INPUT') {
if (e.key === 'Escape') {
document.activeElement.blur();
renderMainMenu();
}
return;
}
playSpeaker(800, 0.03);
switch (e.key) {
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':
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 === '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();