Files
kitchen/docs/api/doc/cookbook.html
2026-04-14 05:35:30 +08:00

569 lines
23 KiB
HTML
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.
<!--
文件名称: cookbook.html
文件作用: 菜谱单页展示 - 每页一道菜品,沉浸式卡片浏览体验
创建时间: 2026-04-14
最后更新: 2026-04-14 - 初始版本创建
作者: AI Assistant
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>如何做饭 - 美味菜谱</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#1a1614;--card-bg:#faf6f1;--text:#2d2420;--text-light:#6b5d52;
--accent:#c45c3e;--accent-light:#e8a090;--gold:#d4a853;--cream:#f5ede3;
--overlay:rgba(26,22,20,0.65);--radius:24px;--shadow:0 25px 80px rgba(0,0,0,.35);
--font-display:'Noto Serif SC',serif;--font-body:'Noto Sans SC',sans-serif;
}
html,body{height:100%;overflow:hidden;background:var(--bg);font-family:var(--font-body);
color:var(--text);-webkit-font-smoothing:antialiased}
.app{position:relative;width:100%;height:100vh;overflow:hidden}
.top-bar{
position:fixed;top:0;left:0;right:0;z-index:100;
display:flex;align-items:center;justify-content:space-between;
padding:16px 24px;padding-top:max(16px,env(safe-area-inset-top));
background:linear-gradient(to bottom,var(--bg) 40%,transparent);
transition:opacity .4s ease
}
.top-bar.hidden{opacity:0;pointer-events:none}
.logo{font-family:var(--font-display);font-size:18px;font-weight:700;color:var(--cream);
letter-spacing:2px;display:flex;align-items:center;gap:8px}
.logo-icon{width:28px;height:28px;border-radius:8px;background:var(--accent);
display:flex;align-items:center;justify-content:center;font-size:16px}
.counter{color:var(--text-light);font-size:13px;font-weight:500;
background:rgba(255,255,255,.08);padding:6px 14px;border-radius:20px;
backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.06)}
.category-nav{
position:fixed;top:64px;left:0;right:0;z-index:99;
display:flex;gap:8px;padding:0 20px;overflow-x:auto;
scrollbar-width:none;-ms-overflow-style:none;
transition:transform .4s cubic-bezier(.4,0,.2,1),opacity .4s ease
}
.category-nav::-webkit-scrollbar{display:none}
.category-nav.hidden{transform:translateY(-120%);opacity:0}
.cat-chip{
flex-shrink:0;padding:7px 18px;border-radius:20px;font-size:13px;
font-weight:500;cursor:pointer;white-space:nowrap;transition:all .3s ease;
background:rgba(255,255,255,.07);color:var(--cream);opacity:.7;
border:1px solid transparent
}
.cat-chip:hover{opacity:1;background:rgba(255,255,255,.12)}
.cat-chip.active{background:var(--accent);color:#fff;opacity:1;
border-color:var(--accent-light);box-shadow:0 4px 16px rgba(196,92,62,.35)}
.viewport{
position:relative;width:100%;height:100%;perspective:1400px;
cursor:grab;cursor:-webkit-grab
}
.viewport:active{cursor:grabbing;cursor:-webkit-grabbing}
.card-container{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
padding:100px 20px 40px
}
.dish-card{
position:relative;width:100%;max-width:520px;height:calc(100vh - 160px);
max-height:800px;border-radius:var(--radius);overflow:hidden;
background:var(--card-bg);box-shadow:var(--shadow);
transform-style:preserve-3d;transition:all .55s cubic-bezier(.4,0,.2,1);
opacity:0;pointer-events:none
}
.dish-card.active{opacity:1;pointer-events:auto;transform:translateZ(0) scale(1)}
.dish-card.prev{transform:translateX(-110%) rotateY(12deg) scale(.88);opacity:.4}
.dish-card.next{transform:translateX(110%) rotateY(-12deg) scale(.88);opacity:.4}
.card-image-wrap{
position:relative;width:100%;height:62%;overflow:hidden;background:var(--cream)
}
.card-image{
width:100%;height:100%;object-fit:cover;
transition:transform .6s cubic-bezier(.4,0,.2,1),filter .4s ease
}
.dish-card.active .card-image{transform:scale(1.02)}
.dish-card.active:hover .card-image{transform:scale(1.06)}
.image-overlay{
position:absolute;bottom:0;left:0;right:0;height:50%;
background:linear-gradient(to top,rgba(0,0,0,.55) 0%,transparent 100%);
pointer-events:none
}
.card-badge{
position:absolute;top:18px;left:18px;z-index:3;
padding:6px 14px;border-radius:12px;font-size:11.5px;font-weight:600;
color:#fff;background:var(--accent);backdrop-filter:blur(12px);
letter-spacing:.5px;text-transform:uppercase;
box-shadow:0 4px 14px rgba(196,92,62,.4)
}
.fav-btn{
position:absolute;top:18px;right:18px;z-index:3;width:40px;height:40px;
border-radius:50%;background:rgba(255,255,255,.9);border:none;
cursor:pointer;display:flex;align-items:center;justify-content:center;
font-size:18px;transition:all .3s ease;box-shadow:0 3px 12px rgba(0,0,0,.15)
}
.fav-btn:hover{transform:scale(1.12)}
.fav-btn.favorited{background:var(--accent);color:#fff}
.fav-btn.favorited svg{fill:#fff}
.card-content{padding:28px 28px 24px;height:38%;display:flex;flex-direction:column}
.card-category{
display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;
color:var(--accent);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:10px
}
.cat-dot{width:6px;height:6px;border-radius:50%;background:var(--accent)}
.card-title{
font-family:var(--font-display);font-size:30px;font-weight:900;line-height:1.25;
color:var(--text);margin-bottom:12px;letter-spacing:-.5px
}
.card-desc{
font-size:14.5px;line-height:1.75;color:var(--text-light);flex:1;
overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;
-webkit-box-orient:vertical
}
.card-footer{
display:flex;align-items:center;justify-content:space-between;
padding-top:16px;border-top:1px solid rgba(0,0,0,.06);margin-top:auto
}
.card-meta{display:flex;gap:16px;font-size:12.5px;color:var(--text-light)}
.meta-item{display:flex;align-items:center;gap:5px}
.nav-hint{font-size:11px;color:var(--text-light);opacity:.5}
.nav-controls{
position:fixed;bottom:0;left:0;right:0;z-index:100;
display:flex;align-items:center;justify-content:center;gap:12px;
padding:16px 24px;padding-bottom:max(16px,env(safe-area-inset-bottom));
background:linear-gradient(to top,var(--bg) 50%,transparent)
}
.nav-btn{
width:48px;height:48px;border-radius:50%;border:none;background:rgba(255,255,255,.08);
color:var(--cream);font-size:20px;cursor:pointer;display:flex;
align-items:center;justify-content:center;transition:all .3s ease;
backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.06)
}
.nav-btn:hover:not(:disabled){background:rgba(255,255,255,.15);transform:scale(1.08)}
.nav-btn:disabled{opacity:.25;cursor:not-allowed}
.nav-btn:active:not(:disabled){transform:scale(.94)}
.progress-track{
width:120px;height:3px;background:rgba(255,255,255,.12);border-radius:2px;
overflow:hidden
}
.progress-fill{
height:100%;background:var(--accent);border-radius:2px;
transition:width .4s cubic-bezier(.4,0,.2,1)
}
.search-overlay{
position:fixed;inset:0;z-index:200;background:rgba(26,22,20,.95);
backdrop-filter:blur(20px);display:flex;flex-direction:column;
align-items:center;justify-content:flex-start;padding-top:120px;
opacity:0;pointer-events:none;transition:all .4s ease
}
.search-overlay.show{opacity:1;pointer-events:auto}
.search-overlay .search-input-wrap{
width:90%;max-width:480px;position:relative
}
.search-overlay input{
width:100%;padding:16px 56px 16px 24px;border-radius:16px;border:1px solid rgba(255,255,255,.15);
background:rgba(255,255,255,.07);color:var(--cream);font-size:17px;
font-family:var(--font-body);outline:none;transition:border-color .3s
}
.search-overlay input:focus{border-color:var(--accent)}
.search-overlay input::placeholder{color:rgba(255,255,255,.35)}
.search-close{
position:absolute;right:10px;top:50%;transform:translateY(-50%);
width:36px;height:36px;border-radius:50%;border:none;background:transparent;
color:var(--cream);font-size:18px;cursor:pointer;display:flex;
align-items:center;justify-content:center
}
.search-results{
width:90%;max-width:480px;margin-top:24px;max-height:50vh;overflow-y:auto
}
.search-item{
display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:14px;
cursor:pointer;transition:background .2s;margin-bottom:4px
}
.search-item:hover{background:rgba(255,255,255,.07)}
.search-item img{width:48px;height:48px;border-radius:10px;object-fit:cover}
.search-item-info h4{color:var(--cream);font-size:15px;font-weight:500}
.search-item-info span{color:rgba(255,255,255,.45);font-size:12px}
.search-btn{
width:40px;height:40px;border-radius:50%;border:none;background:transparent;
color:var(--cream);font-size:18px;cursor:pointer;display:flex;
align-items:center;justify-content:center;transition:all .3s ease
}
.search-btn:hover{background:rgba(255,255,255,.1);border-radius:50%}
.grid-toggle{
position:fixed;bottom:90px;right:24px;z-index:98;
width:46px;height:46px;border-radius:14px;border:none;
background:rgba(255,255,255,.09);color:var(--cream);
font-size:19px;cursor:pointer;display:flex;align-items:center;
justify-content:center;transition:all .3s ease;backdrop-filter:blur(10px);
border:1px solid rgba(255,255,255,.06)
}
.grid-toggle:hover{background:rgba(255,255,255,.15);transform:scale(1.06)}
.grid-view{
position:fixed;inset:0;z-index:150;background:var(--bg);
overflow-y:auto;padding:90px 16px 40px;
opacity:0;pointer-events:none;transition:opacity .4s ease
}
.grid-view.show{opacity:1;pointer-events:auto}
.grid-header{text-align:center;margin-bottom:32px}
.grid-header h2{
font-family:var(--font-display);font-size:28px;font-weight:700;color:var(--cream)
}
.grid-header p{color:var(--text-light);font-size:14px;margin-top:6px;opacity:.6}
.grid-cats{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-bottom:28px}
.grid-cat{
padding:6px 16px;border-radius:20px;font-size:13px;font-weight:500;
cursor:pointer;background:rgba(255,255,255,.07);color:var(--cream);
opacity:.7;transition:all .3s;border:1px solid transparent
}
.grid-cat:hover{opacity:1}
.grid-cat.active{background:var(--accent);color:#fff;opacity:1}
.grid-grid{
display:grid;grid-template-columns:repeat(auto-fill,minmax(155px,1fr));gap:14px;
max-width:900px;margin:0 auto
}
.grid-item{
border-radius:16px;overflow:hidden;background:var(--card-bg);
cursor:pointer;transition:all .3s ease;aspect-ratio:3/4
}
.grid-item:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(0,0,0,.25)}
.grid-item img{width:100%;height:70%;object-fit:cover}
.grid-item-info{padding:10px 12px}
.grid-item-info h4{font-size:13.5px;font-weight:600;color:var(--text);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis
}
.grid-item-info span{font-size:11px;color:var(--text-light)}
.toast{
position:fixed;top:100px;left:50%;transform:translateX(-50%) translateY(-20px);
z-index:300;padding:12px 24px;border-radius:14px;background:var(--accent);
color:#fff;font-size:14px;font-weight:500;opacity:0;pointer-events:none;
transition:all .4s cubic-bezier(.4,0,.2,1);box-shadow:0 8px 30px rgba(196,92,62,.4)
}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
@keyframes fadeInUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}
.dish-card.active .card-content>*,.dish-card.active .card-badge,.dish-card.active .fav-btn{
animation:fadeInUp .5s cubic-bezier(.4,0,.2,1) both
}
.dish-card.active .card-category{animation-delay:.05s}
.dish-card.active .card-title{animation-delay:.1s}
.dish-card.active .card-desc{animation-delay:.15s}
.dish-card.active .card-footer{animation-delay:.2s}
.dish-card.active .card-badge{animation-delay:.05s}
.dish-card.active .fav-btn{animation-delay:.15s}
@media(max-width:600px){
:root{--radius:18px}
.card-container{padding:80px 12px 30px}
.dish-card{max-height:760px;border-radius:18px}
.card-title{font-size:25px}
.card-content{padding:22px 20px 18px}
.top-bar{padding:12px 16px}
.category-nav{top:56px;padding:0 12px}
.cat-chip{padding:5px 14px;font-size:12px}
.nav-btn{width:42px;height:42px}
.grid-grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}
}
</style>
</head>
<body>
<div class="app" id="app">
<div class="top-bar" id="topBar">
<div class="logo"><span class="logo-icon">🍳</span><span>如何做饭</span></div>
<div class="counter" id="counter">1 / 341</div>
<button class="search-btn" id="searchBtn" title="搜索">🔍</button>
</div>
<div class="category-nav" id="catNav"></div>
<div class="viewport" id="viewport">
<div class="card-container" id="cardContainer"></div>
</div>
<div class="nav-controls">
<button class="nav-btn" id="prevBtn" title="上一道"></button>
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
<button class="nav-btn" id="nextBtn" title="下一道"></button>
</div>
<button class="grid-toggle" id="gridToggle" title="网格视图"></button>
</div>
<div class="search-overlay" id="searchOverlay">
<div class="search-input-wrap">
<input type="text" id="searchInput" placeholder="搜索菜谱...">
<button class="search-close" id="searchClose"></button>
</div>
<div class="search-results" id="searchResults"></div>
</div>
<div class="grid-view" id="gridView">
<div class="grid-header">
<h2>全部菜谱</h2>
<p>共 341 道美味佳肴</p>
</div>
<div class="grid-cats" id="gridCats"></div>
<div class="grid-grid" id="gridGrid"></div>
</div>
<div class="toast" id="toast"></div>
<script>
let recipes=[],filteredRecipes=[],currentIndex=0,favorites=new Set(),touchStartX=0,activeCategory='all',CATEGORY_MAP={},DESC_MAP={};
async function init(){
try{
const res=await fetch('recipes.json');
const d=await res.json();
recipes=d.recipes||[];
CATEGORY_MAP=(d._meta&&d._meta.categories)||{};
DESC_MAP=(d._meta&&d._meta.descriptions)||{};
}catch(e){console.error('Failed to load recipes:',e);return}
filteredRecipes=[...recipes];
renderCategories();
renderCards();
bindEvents();
updateUI();
}
function renderCategories(){
const nav=document.getElementById('catNav');
nav.innerHTML='<div class="cat-chip active" data-cat="all">✨ 全部</div>';
Object.keys(CATEGORY_MAP).forEach(cat=>{
const c=CATEGORY_MAP[cat];
const count=recipes.filter(r=>r.category===cat).length;
nav.innerHTML+=`<div class="cat-chip" data-cat="${cat}">${c.icon} ${c.name} (${count})</div>`;
});
nav.querySelectorAll('.cat-chip').forEach(chip=>{
chip.addEventListener('click',()=>filterByCategory(chip.dataset.cat))
});
}
function filterByCategory(cat){
activeCategory=cat;
document.querySelectorAll('.cat-chip').forEach(c=>c.classList.toggle('active',c.dataset.cat===cat));
if(cat==='all')filteredRecipes=[...recipes];
else filteredRecipes=recipes.filter(r=>r.category===cat);
currentIndex=0;
renderCards();
updateUI();
}
function renderCards(){
const container=document.getElementById('cardContainer');
container.innerHTML='';
const total=filteredRecipes.length;
for(let i=0;i<Math.min(3,total);i++){
const idx=(currentIndex+i)%total;
const r=filteredRecipes[idx];
const pos=i===0?'active':i===1?'next':'prev';
container.appendChild(createCard(r,pos,idx));
}
}
function createCard(recipe,pos,index){
const cat=CATEGORY_MAP[recipe.category]||{};
const desc=DESC_MAP[recipe.category]||'一道精心制作的美味佳肴,等待你来品尝与发现。';
const isFav=favorites.has(recipe.name);
const div=document.createElement('div');
div.className='dish-card '+pos;
div.dataset.index=index;
div.innerHTML=`
<div class="card-image-wrap">
<img class="card-image" src="${recipe.image}" alt="${recipe.name}" loading="lazy"
onerror="this.style.background='linear-gradient(135deg,#f5e6d3,#e8d4bc)'">
<div class="image-overlay"></div>
<span class="card-badge">${cat.icon||''} ${cat.name||''}</span>
<button class="fav-btn ${isFav?'favorited':''}" data-name="${recipe.name}"
onclick="toggleFavorite('${recipe.name}',this)" title="收藏">
<svg width="18" height="18" viewBox="0 0 24 24" fill="${isFav?'#fff':'none'}" stroke="${isFav?'#fff':'currentColor'}" stroke-width="2">
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/>
</svg>
</button>
</div>
<div class="card-content">
<div class="card-category"><span class="cat-dot"></span>${cat.name||'菜谱'}</div>
<h2 class="card-title">${recipe.name}</h2>
<p class="card-desc">${desc}</p>
<div class="card-footer">
<div class="card-meta">
<span class="meta-item">⏱ 随时享用</span>
<span class="meta-item">👨‍🍳 家常做法</span>
</div>
<span class="nav-hint">← → 切换</span>
</div>
</div>`;
return div;
}
function updateUI(){
const total=filteredRecipes.length;
if(total===0)return;
document.getElementById('counter').textContent=`${currentIndex+1} / ${total}`;
document.getElementById('progressFill').style.width=((currentIndex+1)/total*100)+'%';
document.getElementById('prevBtn').disabled=currentIndex<=0;
document.getElementById('nextBtn').disabled=currentIndex>=total-1;
}
function go(dir){
const total=filteredRecipes.length;
if(total===0)return;
const newIndex=currentIndex+dir;
if(newIndex<0||newIndex>=total)return;
currentIndex=newIndex;
const cards=document.querySelectorAll('.dish-card');
cards.forEach(card=>{
card.classList.remove('active','prev','next');
const idx=parseInt(card.dataset.index);
if(idx===currentIndex)card.classList.add('active');
else if(idx<currentIndex)card.classList.add('prev');
else card.classList.add('next');
});
const container=document.getElementById('cardContainer');
const lastCard=container.querySelector('[data-index="'+(currentIndex-dir)+'"]');
if(lastCard)lastCard.remove();
const nextIdx=(currentIndex+(dir>0?2:-1)+total)%total;
const r=filteredRecipes[nextIdx];
const pos=dir>0?'next':'prev';
container.appendChild(createCard(r,pos,nextIdx));
updateUI();
}
function toggleFavorite(name,btn){
if(favorites.has(name)){favorites.delete(name);btn.classList.remove('favorited');
btn.querySelector('svg').setAttribute('fill','none');btn.querySelector('svg').setAttribute('stroke','currentColor');
showToast('已取消收藏')}else{favorites.add(name);btn.classList.add('favorited');
btn.querySelector('svg').setAttribute('fill','#fff');btn.querySelector('svg').setAttribute('stroke','#fff');
showToast('已收藏:'+name)}
}
function showToast(msg){
const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
setTimeout(()=>t.classList.remove('show'),1800)
}
function openSearch(){document.getElementById('searchOverlay').classList.add('show');
setTimeout(()=>document.getElementById('searchInput').focus(),100)}
function closeSearch(){document.getElementById('searchOverlay').classList.remove('show');
document.getElementById('searchInput').value='';document.getElementById('searchResults').innerHTML=''}
function doSearch(q){
const results=document.getElementById('results');
const query=q.trim().toLowerCase();
if(!query){document.getElementById('searchResults').innerHTML='';return}
const found=recipes.filter(r=>r.name.toLowerCase().includes(query));
const html=found.slice(0,20).map(r=>`
<div class="search-item" onclick="jumpToDish('${r.name}')">
<img src="${r.image}" alt="${r.name}">
<div class="search-item-info">
<h4>${r.name}</h4><span>${CATEGORY_MAP[r.category]?.name||''}</span>
</div>
</div>`).join('');
document.getElementById('searchResults').innerHTML=html||
'<p style="color:rgba(255,255,255,.4);text-align:center;padding:20px">未找到相关菜谱</p>'
}
function jumpToDish(name){
const idx=filteredRecipes.findIndex(r=>r.name===name);
if(idx!==-1){closeSearch();if(idx!==currentIndex){
while(currentIndex<idx)go(1);while(currentIndex>idx)go(-1)}}else{
closeSearch();
activeCategory='all';document.querySelectorAll('.cat-chip').forEach(c=>c.classList.toggle('active',c.dataset.cat==='all'));
filteredRecipes=[...recipes];const ri=recipes.findIndex(r=>r.name===name);
if(ri!==-1){currentIndex=ri;renderCards();updateUI()}
}
}
function toggleGridView(){
const gv=document.getElementById('gridView');gv.classList.toggle('show');
if(gv.classList.contains('show'))renderGrid()
}
function renderGrid(){
const cats=document.getElementById('gridCats');
cats.innerHTML='<div class="grid-cat active" data-gcat="all">✨ 全部</div>';
Object.keys(CATEGORY_MAP).forEach(cat=>{
const c=CATEGORY_MAP[cat];
cats.innerHTML+=`<div class="grid-cat" data-gcat="${cat}">${c.icon} ${c.name}</div>`
});
cats.querySelectorAll('.grid-cat').forEach(c=>{
c.addEventListener('click',()=>{
cats.querySelectorAll('.grid-cat').forEach(x=>x.classList.remove('active'));
c.classList.add('active');renderGridItems(c.dataset.gcat)
})
});
renderGridItems('all')
}
function renderGridItems(filterCat){
const list=filterCat==='all'?recipes:recipes.filter(r=>r.category===filterCat);
const grid=document.getElementById('gridGrid');
grid.innerHTML=list.map(r=>`
<div class="grid-item" onclick="jumpToDish('${r.name}');toggleGridView()">
<img src="${r.image}" alt="${r.name}" loading="lazy">
<div class="grid-item-info"><h4>${r.name}</h4><span>${CATEGORY_MAP[r.category]?.name||''}</span></div>
</div>`).join('')
}
function bindEvents(){
document.getElementById('prevBtn').addEventListener('click',()=>go(-1));
document.getElementById('nextBtn').addEventListener('click',()=>go(1));
document.getElementById('searchBtn').addEventListener('click',openSearch);
document.getElementById('searchClose').addEventListener('click',closeSearch);
document.getElementById('searchInput').addEventListener('input',e=>doSearch(e.target.value));
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape')closeSearch()});
document.getElementById('searchOverlay').addEventListener('click',e=>{if(e.target.id==='searchOverlay')closeSearch()});
document.getElementById('gridToggle').addEventListener('click',toggleGridView);
const vp=document.getElementById('viewport');
let autoHideTop;
vp.addEventListener('mousemove',()=>{
document.getElementById('topBar').classList.remove('hidden');
document.getElementById('catNav').classList.remove('hidden');
clearTimeout(autoHideTop);
autoHideTop=setTimeout(()=>{
document.getElementById('topBar').classList.add('hidden');
document.getElementById('catNav').classList.add('hidden')
},3000)
});
vp.addEventListener('touchstart',e=>{touchStartX=e.touches[0].clientX},{passive:true});
vp.addEventListener('touchend',e=>{
const diff=e.changedTouches[0].clientX-touchStartX;
if(Math.abs(diff)>60)go(diff<0?1:-1)
},{passive:true});
document.addEventListener('keydown',e=>{
if(document.getElementById('searchOverlay').classList.contains('show'))return;
if(document.getElementById('gridView').classList.contains('show'))return;
if(e.key==='ArrowLeft'||e.key==='ArrowUp'){e.preventDefault();go(-1)}
if(e.key==='ArrowRight'||e.key==='ArrowDown'){e.preventDefault();go(1)}
if(e.key===' '||e.key==='Enter'){
e.preventDefault();
const fav=document.querySelector('.dish-card.active .fav-btn');
if(fav)fav.click()}
});
}
init();
</script>
</body>
</html>