feat(kucharka): initial web resuscitation of Helena & Kamila Heroutova's Pascal cookbook

This commit is contained in:
Krystof Pesek
2026-11-07 18:36:51 +01:00
commit 2050cc9ddf
3 changed files with 1030 additions and 0 deletions
+639
View File
@@ -0,0 +1,639 @@
/* =========================================================================
KUCHAŘKA HELENY A KAMILY HEROUTOVÉ (1998–2026) // PASCAL 7.0 WEB ENGINE
Software-salon ALTÁN (Václavkova 2, Praha 6) & The Observatoř Konzdat
========================================================================= */
// 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) {}
}
// Full Authentic Recipes from Kamila Heroutová's handwritten notebook
const KNIHA_RECEPTU = [
{
id: 1,
nazev: "Zavářená kmínová polévka pro dva",
kategorie: "Polévka",
doba: 30,
suroviny: [
"1 malá cibule jemně nasekaná",
"Kousek másla na zpěnění",
"2 brambory nakrájené na drobné kostičky",
"1 lžička sladké a špetka pálivé papriky",
"Celý český kmín a sůl",
"1 litr mléka (nebo kysaná smetana) s lžící mouky"
],
postup: "Cibuli zpěnit na másle do sklovata. Přidat brambory, papriku a kmín. Zalít vodou a uvařit doměkka. V mléce rozkvedlat lžíci hrubé mouky, vlažné vlít do vroucí polévky a nechat provařit.",
tajemstvi: "Před podáváním zjemnit lžící poctivé kysané smetany a celý kmín před vhozením promnout v dlani pro uvolnění silic."
},
{
id: 2,
nazev: "Babiččino kuře na česneku a slanině",
kategorie: "Hlavní chod",
doba: 85,
suroviny: [
"1 celé kuře naporcované na čtvrtky",
"15 dkg domácí uzené slaniny na kostičky",
"1 celá hlavička třeného českého česneku",
"Kousek másla a sůl s černým pepřem",
"Příloha: vařené brambory s petrželkou"
],
postup: "Do litinového pekáče nasypat kostičky slaniny a česnek. Čtvrtky kuřete osolit, opepřit, položit kůží nahoru na lůžko, poklást plátky másla a pomalu péct přikryté.",
tajemstvi: "Posledních 15 minut odklopit poklop a zvednout žár v troubě, aby kůžička dokonale zkřupala a česnek zezlátl."
},
{
id: 3,
nazev: "Vosí hnízda s rumovým krémem",
kategorie: "Vánoční cukroví",
doba: 50,
suroviny: [
"16 dkg jemně rozdrcených piškotů",
"16 dkg moučkového cukru",
"6 dkg másla, 2 lžíce holandského kakaa",
"2 lžíce mléka a pořádná dávka tuzemského rumu",
"Krém: 5 dkg másla utřeného se žloutkem a vanilkovým cukrem",
"Celé dětské piškoty na přiklopení"
],
postup: "Z těsta vyválet váleček. Kousky vmačkat do formiček vysypaných moučkovým cukrem, udělat důlek, naplnit žloutkovým krémem a zaklopit piškotem.",
tajemstvi: "Formičku před každým kouskem vyprášit moučkovým cukrem, hnízdo pak vyklouzne jedním lehkým klepnutím o vál."
},
{
id: 4,
nazev: "Dělové koule (nepečené kakao)",
kategorie: "Cukroví",
doba: 40,
suroviny: [
"10 dkg pravého změklého másla",
"10 lžic moučkového cukru",
"1 sáček vanilkového cukru",
"1 čerstvý vaječný žloutek",
"2 plné lžíce holandského kakaa",
"Strouhaný kokos nebo mleté vlašské ořechy na obalení"
],
postup: "Máslo utřít s cukrem, kakaem a žloutkem do kompaktní hmoty. Tvarovat kuličky a obalovat v kokosu či ořeších. Nechat odležet v chladu.",
tajemstvi: "Přímo do kakaového základu kápnout tři kapky rumu pro sametový lesk a plnost čokoládové chuti."
},
{
id: 5,
nazev: "Kočičí oči s rybízovou marmeládou",
kategorie: "Pečení",
doba: 45,
suroviny: [
"10 dkg hladké mouky",
"8 dkg poctivého másla",
"3 dkg moučkového cukru",
"1 vaječný žloutek",
"Ostrá domácí rybízová marmeláda"
],
postup: "Křehké těsto nechat odpočinout v chladu. Vyválet na tenko, vykrajovat plná kolečka a kolečka se třemi dírkami. Péct do světle růžova a slepovat rybízem.",
tajemstvi: "Rybízovou marmeládu před mazáním mírně nahřát v kastrůlku, aby se dokonale vpila do křehkého těsta."
},
{
id: 6,
nazev: "Sváteční taštičky s vepřovým masem",
kategorie: "Sváteční pečení",
doba: 55,
suroviny: [
"2 balíčky listového těsta",
"30 dkg mletého vepřového masa",
"1 cibule nasekaná najemno",
"Domácí slanina na kostičky",
"1 vejce do náplně, 1 vejce na potření",
"Jemně strouhaná citronová kůra a pepř"
],
postup: "Mleté maso orestovat na cibulce se slaninou a citronovou kůrou. Z těsta vyválet čtverce, plnit masem, okraje stisknout vidličkou, potřít vejcem a péct zprudka v horké troubě.",
tajemstvi: "Citronová kůra dodá vepřové náplni neuvěřitelnou svěžest a lehkost (ochranná známka Kamily)."
},
{
id: 7,
nazev: "Zapečené fleky se špenátem a uzeným masem",
kategorie: "Hlavní chod",
doba: 40,
suroviny: [
"Těstoviny fleky",
"Listový špenát čerstvý či drcený",
"Uzené maso nebo klobása na kostičky",
"3 vejce a trochu plnotučného mléka",
"4 stroužky třeného českého česneku"
],
postup: "Fleky uvařit al dente, špenát podusit na česneku. Promíchat s uzeným masem, rozprostřít do vymazaného pekáčku a zapéct zalité rozšlehaným vejcem s mlékem.",
tajemstvi: "Do vaječné zálivky přistrouhnout čerstvý muškátový oříšek a na povrch položit plátky uzeného boku."
},
{
id: 8,
nazev: "Kuřecí srdíčka na smetaně s rýží",
kategorie: "Hlavní chod",
doba: 50,
suroviny: [
"Čerstvá kuřecí srdíčka (500g)",
"Hustá 33% smetana ke šlehání",
"Rýže parboiled nebo jasmínová",
"Cibule a poctivé máslo",
"Nové koření, bobkový list a černý pepř"
],
postup: "Srdíčka očistit a podusit na cibulce s divokým kořením doměkka. Vyjmout koření, zalít smetanou a nechat zredukovat do sametové omáčky.",
tajemstvi: "Omáčku nezahušťovat moukou! Nechat ji přirozeně zhoustnout pouhou redukcí poctivé smetany."
}
];
// Historical Database records (510 entries sample from 1998-2021 Bohnice)
const HISTORICKA_JIDLA = [
{ nazev: "PIZZA [založení deníku v Bohnicích]", datum: "03.08.1998", typ: "Večeře", vareni: 12, tagy: ["pizza", "syr", "testo"] },
{ nazev: "Špenát, uzené maso, bramborový knedlík", datum: "04.08.1998", typ: "Večeře", vareni: 19, tagy: ["spenat", "maso", "knedlik", "uzene"] },
{ nazev: "Kuře, nudle, ananas (Kryštofovo oblíbené)", datum: "06.09.1998", typ: "Oběd", vareni: 18, tagy: ["kure", "nudle", "ananas", "maso"] },
{ nazev: "Maso – uzený jazyk, brambory, obloha", datum: "13.09.1998", typ: "Oslava", vareni: 7, tagy: ["maso", "brambory", "jazyk"] },
{ nazev: "Smažený sýr, vařené brambory a tatarka", datum: "14.05.2021", typ: "Večeře", vareni: 32, tagy: ["syr", "brambory", "tatarka", "vejce"] },
{ nazev: "Špagety milánské se sýrem a masem", datum: "10.04.2021", typ: "Oběd", vareni: 29, tagy: ["spagety", "maso", "syr", "rajcata"] },
{ nazev: "Domácí lečo s klobásou a čerstvým chlebem", datum: "12.08.2020", typ: "Večeře", vareni: 27, tagy: ["leco", "vejce", "klobasa", "chleb"] },
{ nazev: "Rizoto se zeleninou, kuřecím masem a sýrem", datum: "02.03.2021", typ: "Oběd", vareni: 25, tagy: ["ryze", "kure", "zelenina", "syr"] },
{ nazev: "Smažený vepřový řízek s bramborovým salátem", datum: "23.02.2019", typ: "Oběd", vareni: 24, tagy: ["maso", "brambory", "vejce", "salat"] },
{ nazev: "Královská drůbeží sekaná s bramborovou kaší", datum: "15.01.2021", typ: "Oběd", vareni: 23, tagy: ["maso", "brambory", "kase", "mleko"] },
{ nazev: "Křupavý bramborák s česnekem a majoránkou", datum: "05.11.2020", typ: "Večeře", vareni: 22, tagy: ["brambory", "cesnek", "vejce", "majoranka"] },
{ nazev: "Fazolky na kyselo se smetanou a bramborem", datum: "18.06.2020", typ: "Oběd", vareni: 17, tagy: ["fazolky", "smetana", "brambory", "ocet"] },
{ nazev: "Hovězí guláš s cibulí a houskovým knedlíkem", datum: "20.10.2019", typ: "Oběd", vareni: 20, tagy: ["maso", "hovezi", "cibule", "knedlik"] },
{ nazev: "Palačinky s tvarohem a jahodovou marmeládou", datum: "11.04.2020", typ: "Večeře", vareni: 16, tagy: ["mleko", "mouka", "vejce", "tvaroh"] },
{ nazev: "Květákový mozeček s vařeným bramborem", datum: "28.05.2019", typ: "Oběd", vareni: 14, tagy: ["kvetak", "vejce", "brambory", "cibule"] }
];
// App State
let currentView = 'menu'; // 'menu', 'recept', 'recepty_list', 'suroviny', 'davno', 'oblibena', 'kronika', 'altan'
let currentRecipeId = 1;
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)
function norm(str) {
return (str || "").toLowerCase()
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]/g, "");
}
// Views renderers
function renderMainMenu() {
currentView = 'menu';
winTitleEl.textContent = "KUCHAŘKA HELENY A KAMILY HEROUTOVÉ // HLAVNÍ NABÍDKA";
let html = `
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
KUCHAŘKA HELENY A KAMILY HEROUTOVÉ // SOFTWARE-SALON ALTÁN (1998–2026)
Dejvice, Václavkova 2 & Bohnice | Rekonstrukce v Pascalu pro The Observatoř
════════════════════════════════════════════════════════════════════════════════</div>
<div class="dos-box">
<span style="color:var(--dos-yellow)">Vítejte v rodinném softwarovém archivu receptů a jídelníčků.</span>
Tento systém propojil procedurální přesnost Turbo Pascalu s každodenní
péčí o teplo domova a společný stůl po více než dvě dekády.
</div>
<div class="dos-header" style="color:var(--dos-cyan)">── RUČNĚ PSANÝ SEŠIT RECEPTŮ KAMILY HEROUTOVÉ ─────────────────────────────</div>
<div class="dos-menu-list">
`;
KNIHA_RECEPTU.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">${r.kategorie} (${r.doba} min)</span>
</div>`;
});
html += `</div>
<div class="dos-header" style="color:var(--dos-cyan)">── ALGORITMICKÉ MODULY & VAŘENÍ (KUCHARKA.EXE) ─────────────────────────────</div>
<div class="dos-menu-list">
<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="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č 510 záznamů JÍDLA 1998–2021)</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 style="margin-top:20px; color:var(--dos-grey)">
Zvolte číslo <span class="hotkey">[1-8]</span> nebo klávesu <span class="hotkey">[S, D, O, K, A]</span>. Na mobilu klepněte prstem.
</div>
`;
screenEl.innerHTML = html;
}
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}]: ${r.nazev.toUpperCase()}`;
let html = `
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
RECEPT Č. ${r.id}: ${r.nazev}
Kategorie: ${r.kategorie} | Odhadovaná doba přípravy: ${r.doba} minut
════════════════════════════════════════════════════════════════════════════════</div>
<div class="dos-header" style="color:var(--dos-cyan)">── POTŘEBNÉ SUROVINY ───────────────────────────────────────────────────────</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 ──────────────────────────────────────────────</div>
<div class="dos-box" style="line-height:1.5; color:var(--dos-white)">
${r.postup}
</div>
<div class="secret-badge">
<strong>★ TAJEMSTVÍ KAMILY HEROUTOVÉ:</strong><br>
<em>"${r.tajemstvi}"</em>
</div>
<div style="margin-top:20px; display:flex; gap:10px">
<button class="dos-btn" onclick="renderMainMenu()">[ESC] ZPĚT DO NABÍDKY</button>
${id > 1 ? `<button class="dos-btn" onclick="openRecipe(${id - 1})">[◄] PŘEDCHOZÍ</button>` : ''}
${id < KNIHA_RECEPTU.length ? `<button class="dos-btn" onclick="openRecipe(${id + 1})">[►] DALŠÍ</button>` : ''}
</div>
`;
screenEl.innerHTML = html;
}
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 porovná databázi receptů Kamily i 510 historických jídel a spočítá shodu.
</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, kure" 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
const scoredRecipes = KNIHA_RECEPTU.map(r => {
let matches = [];
r.suroviny.forEach(sur => {
const nSur = norm(sur);
tokens.forEach(tok => {
if (nSur.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 = [];
j.tagy.forEach(tag => {
const nTag = norm(tag);
tokens.forEach(tok => {
if (nTag.includes(tok) || norm(j.nazev).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}"</div>`;
if (scoredRecipes.length > 0) {
html += `<div style="color:var(--dos-yellow); margin:10px 0; font-weight:bold">★ RECEPTY Z RUČNÍHO SEŠITU KAMILY:</div>`;
scoredRecipes.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.nazev}</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, kure.</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, 8).forEach((j, idx) => {
html += `<div style="padding:6px 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.nazev}</span></span>
<span><span style="color:var(--dos-yellow)">Naposledy: ${j.datum}</span> | Celkem vařeno: ${j.vareni}×</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";
const top = [...HISTORICKA_JIDLA].sort((a, b) => b.vareni - a.vareni);
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 510 záznamů rodinného jídelníčku z let 1998–2021:
</div>
<div class="dos-menu-list">
`;
top.slice(0, 10).forEach((j, idx) => {
const barWidth = Math.round((j.vareni / top[0].vareni) * 25);
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:340px">${j.nazev}</span>
<span style="color:var(--dos-cyan)">${bar}</span>
<span style="color:var(--dos-yellow); margin-left:10px">${j.vareni}×</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() {
currentView = 'kronika';
playSpeaker(650, 0.05);
winTitleEl.textContent = "KRONIKA RODINNÉHO STOLU // 510 ZÁZNAMŮ BOHNICE (1998–2021)";
let html = `
<div class="dos-header">════════════════════════════════════════════════════════════════════════════════
KRONIKA RODINNÉHO STOLU // BOHNICE & DEJVICE (1998–2021)
════════════════════════════════════════════════════════════════════════════════</div>
<div class="dos-box">
Autentické záznamy 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:10px">
`;
HISTORICKA_JIDLA.forEach(j => {
html += `<div style="padding:4px 0; border-bottom:1px dotted rgba(0,170,170,0.4)">
<span style="color:var(--dos-yellow); font-weight:bold">${j.datum}</span>
<span class="tag-badge">[${j.typ}]</span>
<span style="color:var(--dos-white)">${j.nazev}</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 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.
Tato webová aplikace věrně rekonstruuje veškeré algoritmy a zachovává
rodinný odkaz pro další 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 search input, don't hijack all keys except 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 '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8':
openRecipe(parseInt(e.key));
break;
case 's': case 'S': case 'F3':
e.preventDefault();
openSuroviny();
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 'F2':
e.preventDefault();
renderMainMenu();
break;
case 'Escape':
renderMainMenu();
break;
}
});
// Touch button handler
document.querySelectorAll('.t-btn').forEach(btn => {
btn.addEventListener('click', () => {
const key = btn.getAttribute('data-key');
if (key === 'Escape') renderMainMenu();
else if (key === 's') openSuroviny();
else if (key === 'd') openDavno();
else if (key === 'o') openOblibena();
else if (key === 'k') openKronika();
else {
const num = parseInt(key);
if (!isNaN(num)) openRecipe(num);
}
});
});
// Menu bar clicks
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', () => {
const act = item.getAttribute('data-act');
if (act === 'menu' || act === 'recepty') renderMainMenu();
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();
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>KUCHAŘKA // Helena & Kamila Heroutová (1998–2026)</title>
<link rel="stylesheet" href="style.css">
</head>
<body class="crt-screen">
<div id="dos-container">
<!-- Turbo Pascal Top Menu Bar -->
<header id="menu-bar">
<span class="menu-item active" data-act="menu"><span class="hotkey">S</span>oubor</span>
<span class="menu-item" data-act="recepty"><span class="hotkey">R</span>eceptář</span>
<span class="menu-item" data-act="suroviny"><span class="hotkey">Š</span>pajz</span>
<span class="menu-item" data-act="davno"><span class="hotkey">D</span>ávno</span>
<span class="menu-item" data-act="oblibena"><span class="hotkey">O</span>blíbená</span>
<span class="menu-item" data-act="kronika"><span class="hotkey">K</span>ronika</span>
<span class="menu-item" data-act="altan"><span class="hotkey">A</span>ltán</span>
<span class="menu-spacer"></span>
<span class="time-display" id="time-display">18:35:00</span>
</header>
<!-- Main CRT Terminal Window -->
<main id="terminal-window">
<div class="window-frame">
<div class="window-title-bar">
<span class="win-btn">[■]</span>
<span class="win-title" id="win-title">KUCHAŘKA HELENY A KAMILY HEROUTOVÉ // PASCAL 7.0</span>
<span class="win-num">[1]</span>
</div>
<div class="window-content" id="screen-content">
<!-- Dynamic Turbo Pascal Content Injected Here -->
</div>
</div>
</main>
<!-- Mobile Touch Keyboard Bar -->
<nav id="touch-controls">
<button class="t-btn" data-key="1">1</button>
<button class="t-btn" data-key="2">2</button>
<button class="t-btn" data-key="3">3</button>
<button class="t-btn" data-key="4">4</button>
<button class="t-btn" data-key="5">5</button>
<button class="t-btn" data-key="6">6</button>
<button class="t-btn" data-key="7">7</button>
<button class="t-btn" data-key="8">8</button>
<button class="t-btn highlight" data-key="s">ŠPAJZ [S]</button>
<button class="t-btn" data-key="d">DÁVNO [D]</button>
<button class="t-btn" data-key="o">TOP [O]</button>
<button class="t-btn" data-key="k">KRONIKA [K]</button>
<button class="t-btn esc" data-key="Escape">ESC [MENU]</button>
</nav>
<!-- Turbo Pascal Bottom Status Bar -->
<footer id="status-bar">
<span class="f-key"><span class="f-num">F1</span> Nápověda</span>
<span class="f-key"><span class="f-num">F2</span> Receptář</span>
<span class="f-key"><span class="f-num">F3</span> Vařit ze špajzu</span>
<span class="f-key"><span class="f-num">F4</span> Dávno nevařeno</span>
<span class="f-key"><span class="f-num">F5</span> Kronika 510</span>
<span class="f-key"><span class="f-num">ESC</span> Hlavní menu</span>
<span class="f-key sound-toggle" id="sound-btn">🔊 ZVUK: ON</span>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>
+322
View File
@@ -0,0 +1,322 @@
/* =========================================================================
KUCHAŘKA V PASCALU // BORLAND TURBO PASCAL 7.0 & FREE PASCAL CRT PALETTE
========================================================================= */
@font-face {
font-family: 'VGAFont';
src: local('IBM Plex Mono'), local('Courier New'), monospace;
}
:root {
--dos-blue: #0000a8;
--dos-cyan: #00aaaa;
--dos-grey: #aaaaaa;
--dos-white: #ffffff;
--dos-yellow: #ffff55;
--dos-green: #55ff55;
--dos-red: #ff5555;
--dos-black: #000000;
--dos-darkgrey: #555555;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
html, body {
width: 100%;
height: 100%;
background-color: #050510;
color: var(--dos-grey);
font-family: 'VGAFont', monospace;
font-size: 15px;
line-height: 1.35;
overflow: hidden;
}
/* CRT Scanline & Phosphor Overlay */
.crt-screen::before {
content: " ";
display: block;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
z-index: 99;
background-size: 100% 3px, 6px 100%;
pointer-events: none;
}
#dos-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--dos-blue);
position: relative;
box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.6);
}
/* Top Turbo Pascal Menu Bar */
#menu-bar {
background-color: var(--dos-grey);
color: var(--dos-black);
height: 26px;
display: flex;
align-items: center;
padding: 0 10px;
font-weight: bold;
border-bottom: 1px solid var(--dos-black);
gap: 16px;
font-size: 14px;
}
.menu-item {
cursor: pointer;
padding: 2px 6px;
}
.menu-item:hover, .menu-item.active {
background-color: var(--dos-cyan);
color: var(--dos-white);
}
.hotkey {
color: var(--dos-yellow);
text-shadow: 1px 0 0 var(--dos-black);
}
.menu-spacer {
flex: 1;
}
.time-display {
color: var(--dos-black);
font-weight: normal;
font-size: 13px;
}
/* Main Terminal Window Frame */
#terminal-window {
flex: 1;
padding: 10px;
display: flex;
overflow: hidden;
}
.window-frame {
flex: 1;
border: 2px solid var(--dos-cyan);
background-color: var(--dos-blue);
display: flex;
flex-direction: column;
position: relative;
box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.4);
}
.window-title-bar {
background-color: var(--dos-cyan);
color: var(--dos-white);
display: flex;
justify-content: space-between;
align-items: center;
padding: 2px 10px;
font-weight: bold;
font-size: 14px;
}
.win-title {
color: var(--dos-yellow);
letter-spacing: 1px;
}
.window-content {
flex: 1;
padding: 16px 20px;
overflow-y: auto;
white-space: pre-wrap;
color: var(--dos-white);
font-size: 15px;
}
/* Scrollbar in DOS Style */
.window-content::-webkit-scrollbar {
width: 14px;
}
.window-content::-webkit-scrollbar-track {
background: var(--dos-blue);
border-left: 1px solid var(--dos-cyan);
}
.window-content::-webkit-scrollbar-thumb {
background: var(--dos-cyan);
}
/* Typographic Elements inside CRT */
.dos-header {
color: var(--dos-yellow);
font-weight: bold;
margin-bottom: 12px;
}
.dos-box {
border: 1px solid var(--dos-cyan);
padding: 10px 14px;
margin: 12px 0;
background-color: rgba(0, 0, 100, 0.4);
}
.dos-menu-list {
margin: 12px 0;
}
.dos-menu-item {
padding: 4px 8px;
cursor: pointer;
display: flex;
gap: 10px;
}
.dos-menu-item:hover, .dos-menu-item.selected {
background-color: var(--dos-cyan);
color: var(--dos-black);
font-weight: bold;
}
.dos-menu-item:hover .hotkey, .dos-menu-item.selected .hotkey {
color: var(--dos-white);
text-shadow: none;
}
.dos-input-group {
display: flex;
align-items: center;
gap: 10px;
margin: 16px 0;
}
.dos-input {
background-color: var(--dos-black);
color: var(--dos-yellow);
border: 1px solid var(--dos-cyan);
font-family: inherit;
font-size: 16px;
padding: 4px 8px;
flex: 1;
outline: none;
}
.dos-btn {
background-color: var(--dos-cyan);
color: var(--dos-black);
border: 1px solid var(--dos-white);
padding: 4px 14px;
font-family: inherit;
font-weight: bold;
cursor: pointer;
}
.dos-btn:hover {
background-color: var(--dos-yellow);
}
.tag-badge {
background-color: rgba(0, 170, 170, 0.3);
color: var(--dos-cyan);
padding: 1px 6px;
border-radius: 2px;
font-size: 12px;
}
.secret-badge {
color: var(--dos-yellow);
background-color: rgba(255, 255, 85, 0.15);
border-left: 3px solid var(--dos-yellow);
padding: 6px 12px;
margin: 10px 0;
}
/* Touch controls on mobile */
#touch-controls {
display: none;
background-color: var(--dos-black);
padding: 6px;
gap: 6px;
overflow-x: auto;
border-top: 1px solid var(--dos-cyan);
}
.t-btn {
background-color: var(--dos-blue);
color: var(--dos-yellow);
border: 1px solid var(--dos-cyan);
padding: 6px 10px;
font-family: inherit;
font-size: 13px;
font-weight: bold;
white-space: nowrap;
border-radius: 2px;
}
.t-btn.highlight {
background-color: var(--dos-cyan);
color: var(--dos-black);
}
.t-btn.esc {
background-color: var(--dos-red);
color: var(--dos-white);
}
/* Status Bar at bottom */
#status-bar {
background-color: var(--dos-cyan);
color: var(--dos-black);
height: 26px;
display: flex;
align-items: center;
padding: 0 10px;
gap: 16px;
font-size: 13px;
font-weight: bold;
border-top: 1px solid var(--dos-black);
}
.f-key {
display: flex;
gap: 4px;
cursor: pointer;
}
.f-num {
color: var(--dos-white);
background-color: var(--dos-black);
padding: 0 4px;
border-radius: 2px;
}
.sound-toggle {
margin-left: auto;
color: var(--dos-yellow);
cursor: pointer;
}
/* Mobile responsive breakpoints */
@media (max-width: 768px) {
#touch-controls {
display: flex;
}
#status-bar {
display: none;
}
#menu-bar {
font-size: 12px;
gap: 8px;
}
.window-content {
padding: 10px;
font-size: 13px;
}
}