今餐食乜餸

<!DOCTYPE html>
<html lang="zh-HK">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>今餐食乜餸 - 精準食譜版</title>
<style>
  body {
    margin: 0;
    background: #f5f7fb;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Arial, sans-serif;
    color: #111827;
  }
  .wrap { max-width: 980px; margin: 0 auto; padding: 24px 16px 40px; }
  .card {
    background: #fff; border: 1px solid #e5e7eb; border-radius: 20px;
    box-shadow: 0 10px 24px rgba(0,0,0,0.05); padding: 20px;
  }
  h1 { margin: 0 0 8px; font-size: 1.8rem; }
  .sub { margin: 0 0 18px; color: #4b5563; line-height: 1.5; }
  .controls { display: grid; grid-template-columns: 1fr 1fr auto; gap: 12px; align-items: end; }
  label { display: block; font-weight: 700; margin-bottom: 6px; }
  select, button { border-radius: 12px; min-height: 48px; font-size: 1rem; }
  select { width: 100%; border: 1px solid #d1d5db; padding: 12px 14px; background: #fff; }
  .actions { display: flex; gap: 8px; }
  .btn { border: none; padding: 12px 16px; font-weight: 700; cursor: pointer; transition: 0.2s; }
  .btn:hover { opacity: 0.9; }
  .btn-primary { background: #111827; color: #fff; }
  .btn-secondary { background: #f3f4f6; color: #111827; }
  
  .status { margin-top: 14px; min-height: 1.4em; color: #374151; }
  .panel { margin-top: 16px; border: 1px solid #e5e7eb; border-radius: 16px; padding: 16px; background: #fafafa; }
  .badge { display: inline-block; padding: 4px 10px; border-radius: 999px; background: #eef2ff; color: #3730a3; font-size: .85rem; font-weight: 700; margin-right: 8px; }
  .badge-discount { background: #fef2f2; color: #991b1b; }
  h2 { margin: 12px 0 10px; font-size: 1.15rem; }
  
  ul.menu { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
  ul.menu li { padding: 14px 16px; border-radius: 12px; border: 1px solid rgba(0,0,0,0.05); display: flex; flex-direction: column; gap: 10px; }
  .dish-header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
  
  .links { display: flex; flex-wrap: wrap; gap: 8px; }
  button.link-btn, a.link { 
    display: inline-block; text-decoration: none; padding: 8px 12px; 
    border-radius: 8px; font-size: .9rem; font-weight: 600; cursor: pointer; border: 1px solid transparent; 
  }
  button.link-btn { background: #fff; color: #0f766e; border-color: #a5f3fc; }
  a.link-yt { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
  a.link-shop { background: #f0fdf4; color: #15803d; border-color: #bbf7d0; }
  
  /* 食譜展開內容 */
  .recipe-content { 
    display: none; background: #fff; border-radius: 8px; padding: 16px; 
    margin-top: 8px; border: 1px solid #e5e7eb; font-size: 0.95rem;
  }
  .recipe-content.active { display: block; animation: fadeIn 0.3s ease; }
  .recipe-content h4 { margin: 0 0 8px; color: #111827; font-size: 1rem; }
  .recipe-content ul, .recipe-content ol { margin: 0 0 12px; padding-left: 20px; color: #4b5563; }
  .recipe-content li { margin-bottom: 4px; }
  
  .section { margin-top: 16px; padding-top: 14px; border-top: 1px solid #e5e7eb; }
  .section h3 { margin: 0 0 10px; font-size: 1rem; }
  .pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
  .pill { display: inline-block; padding: 4px 10px; border-radius: 999px; background: #f3f4f6; font-size: .85rem; }
  .pill-discount { background: #fee2e2; color: #991b1b; font-weight: bold; }
  
  .note { margin-top: 10px; color: #6b7280; font-size: .92rem; }
  
  @keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } }
  @media (max-width: 720px) {
    .controls { grid-template-columns: 1fr; }
    .actions { flex-direction: column; }
  }
</style>
</head>
<body>
<div class="wrap">
  <div class="card">
    <h1>今餐食乜餸</h1>
    <p class="sub">內置精準匹配嘅「食材與步驟」食譜,確保同教學影片一致。附帶買餸指南,保證每味餸隔 10 次先會再出現。</p>

    <div class="controls">
      <div>
        <label for="city">你而家喺邊度?</label>
        <select id="city"></select>
      </div>
      <div>
        <label for="mealType">想食咩類型?</label>
        <select id="mealType">
          <option value="dinner">三餸一湯 (住家飯)</option>
          <option value="tea">港式茶餐 (粉麵/快餐)</option>
        </select>
      </div>
      <div class="actions">
        <button class="btn btn-primary" id="generate">幫我諗食乜</button>
        <button class="btn btn-secondary" id="reset">清除紀錄</button>
      </div>
    </div>

    <div class="status" id="status"></div>
    <div id="result"></div>
  </div>
</div>

<script>
// 切換顯示食譜內容
function toggleRecipe(id) {
  const el = document.getElementById(id);
  if (el.classList.contains('active')) {
    el.classList.remove('active');
  } else {
    el.classList.add('active');
  }
}

(function () {
  const STORAGE_KEY = 'jcsm_history_v4';
  const MAX_HISTORY = 10;
  const pastelColors = ['#fef2f2', '#fff7ed', '#f0fdf4', '#eff6ff', '#fdf2f8', '#f5f3ff', '#ecfeff', '#fefce8'];

  const ingredientCatalog = {
    eggs: { label: '雞蛋' }, chicken: { label: '雞肉' }, pork: { label: '豬肉' },
    beef: { label: '牛肉' }, fish: { label: '魚類' }, shrimp: { label: '蝦' },
    clam: { label: '蜆' }, tofu: { label: '豆腐' }, leafy_greens: { label: '葉菜' },
    tomato: { label: '番茄' }, potato: { label: '薯仔' }, eggplant: { label: '茄子' },
    cucumber: { label: '青瓜' }, mushroom: { label: '菇類' }, broccoli: { label: '西蘭花' },
    corn: { label: '粟米' }, noodles: { label: '粉麵' }, macaroni: { label: '通粉' },
    luncheon_meat: { label: '午餐肉' }, satay: { label: '沙嗲醬' }, ginger: { label: '薑' },
    onion: { label: '洋蔥' }
  };

  const cities = {
    hong_kong: { 
      label: 'Hong Kong 香港', 
      shops: [{name: 'HKTVmall', url: 'https://www.hktvmall.com/'}, {name: '百佳', url: 'https://www.pns.hk/'}] 
    },
    london: { 
      label: 'London 倫敦', 
      shops: [{name: '龍鳳行', url: 'https://www.loonfung.com/'}, {name: 'Tesco', url: 'https://www.tesco.com/'}] 
    },
    vancouver: { 
      label: 'Vancouver 溫哥華', 
      shops: [{name: '大統華', url: 'https://www.tntsupermarket.com/'}, {name: 'Walmart', url: 'https://www.walmart.ca/'}] 
    },
    sydney: { 
      label: 'Sydney 悉尼', 
      shops: [{name: '通利', url: 'https://tongli.com.au/'}, {name: 'Woolworths', url: 'https://www.woolworths.com.au/'}] 
    }
  };

  // 內置精準食譜資料庫
  const dishes = [
    { 
      id:'tomato_egg', type:'main', name:'番茄炒蛋', required:['eggs','tomato'],
      recipe: {
        ingredients: ['番茄 2個 (切塊)', '雞蛋 3隻 (打勻)', '蔥花 少許', '糖 1茶匙', '鹽 少許', '茄汁 1湯匙'],
        steps: ['燒熱油鑊,將雞蛋炒至半熟,盛起備用。', '原鑊加少許油,爆香番茄塊。', '加入糖、鹽、茄汁及少許水,煮至番茄軟身出汁。', '將雞蛋回鑊,與番茄炒勻,撒上蔥花即成。']
      }
    },
    { 
      id:'chicken_potato_onion', type:'main', name:'洋蔥薯仔炆雞', required:['chicken','potato','onion'],
      recipe: {
        ingredients: ['雞件 半隻', '薯仔 2個 (切塊)', '洋蔥 1個 (切塊)', '生抽、老抽、糖、生粉 (醃料)'],
        steps: ['雞件用醃料醃20分鐘。', '燒熱油,先將薯仔煎至表面微黃,盛起。', '爆香洋蔥,加入雞件炒至表面變色。', '加入薯仔及適量清水,加蓋中火炆15分鐘至汁濃即成。']
      }
    },
    { 
      id:'broccoli_beef', type:'main', name:'西蘭花炒牛肉', required:['beef','broccoli'],
      recipe: {
        ingredients: ['牛肉 200g (切片)', '西蘭花 1個 (切小朵)', '蒜蓉 1湯匙', '蠔油 1湯匙'],
        steps: ['牛肉用生抽、糖、生粉醃15分鐘。', '西蘭花放入滾水灼2分鐘,撈起瀝乾。', '燒熱油鑊,爆香蒜蓉,放入牛肉快炒至七成熟。', '加入西蘭花及蠔油炒勻,埋芡即成。']
      }
    },
    { 
      id:'steamed_pork_patty', type:'main', name:'梅菜蒸肉餅', required:['pork'],
      recipe: {
        ingredients: ['免治豬肉 300g', '甜梅菜 1棵', '生抽 1茶匙', '糖 半茶匙', '生粉 1茶匙', '水 2湯匙'],
        steps: ['梅菜浸洗乾淨,擠乾水份後切碎。', '免治豬肉加入醃料及水,順同一方向攪拌至起膠。', '拌入梅菜碎,平鋪在蒸碟上。', '水滾後大火蒸12-15分鐘至熟透。']
      }
    },
    { 
      id:'steamed_fish', type:'main', name:'清蒸石斑', required:['fish','ginger'],
      recipe: {
        ingredients: ['石斑魚 1條', '薑絲 適量', '蔥絲 適量', '蒸魚豉油 2湯匙', '熟油 2湯匙'],
        steps: ['魚洗淨抹乾,碟底鋪少許薑絲,放上魚,魚面再放薑絲。', '水滾後隔水大火蒸8-10分鐘。', '倒去碟內多餘水份,鋪上蔥絲。', '燒熱2湯匙油淋在蔥絲上,最後淋上蒸魚豉油即成。']
      }
    },
    { 
      id:'tomato_potato_soup', type:'soup', name:'番茄薯仔排骨湯', required:['tomato','potato','pork'],
      recipe: {
        ingredients: ['番茄 3個', '薯仔 2個', '排骨 300g', '薑 2片', '鹽 適量'],
        steps: ['排骨飛水洗淨。番茄及薯仔切塊。', '鍋中加入適量清水及薑片,放入排骨大火煲滾。', '轉中小火煲30分鐘,加入番茄及薯仔。', '繼續煲45分鐘,最後加鹽調味即成。']
      }
    },
    { 
      id:'satay_beef_noodles', type:'tea', name:'沙嗲牛肉麵', required:['beef','noodles','satay'],
      recipe: {
        ingredients: ['牛肉 150g', '即食麵 1包', '沙嗲醬 2湯匙', '花生醬 1茶匙', '生抽、糖 (醃料)'],
        steps: ['牛肉醃15分鐘。', '燒熱油,爆香沙嗲醬及花生醬,加入牛肉炒熟,加少許水煮成濃汁。', '另起鍋煮熟即食麵及湯底。', '將沙嗲牛肉連汁鋪在麵上即成。']
      }
    }
  ];

  // 補全剩餘食譜(簡單版)以防報錯
  const allDishes = dishes.concat([
    { id:'blackbean_clam', type:'main', name:'豉椒炒蜆', required:['clam'], recipe: { ingredients:['蜆 1斤','豆豉 1湯匙','辣椒 適量'], steps:['蜆吐沙飛水','爆香豆豉辣椒','落蜆炒勻'] } },
    { id:'ketchup_shrimp', type:'main', name:'茄汁蝦碌', required:['shrimp','tomato'], recipe: { ingredients:['蝦 半斤','茄汁 3湯匙'], steps:['蝦剪鬚煎香','落茄汁煮勻'] } },
    { id:'cold_eggplant', type:'main', name:'涼拌茄子', required:['eggplant'], recipe: { ingredients:['茄子 2條','蒜蓉醋汁'], steps:['茄子蒸熟撕條','淋上醬汁'] } },
    { id:'garlic_cucumber', type:'main', name:'手拍青瓜', required:['cucumber'], recipe: { ingredients:['青瓜 2條','蒜蓉、醋、麻油'], steps:['青瓜拍碎切塊','加入調味料拌勻'] } },
    { id:'corn_pork_soup', type:'soup', name:'粟米排骨湯', required:['corn','pork'], recipe: { ingredients:['粟米 2條','排骨 300g'], steps:['排骨飛水','全部材料煲1.5小時'] } },
    { id:'tomato_beef_macaroni', type:'tea', name:'番茄牛肉通粉', required:['beef','tomato','macaroni'], recipe: { ingredients:['牛肉 100g','番茄 2個','通粉 1碗'], steps:['煮熟通粉','番茄煮成湯底','灼熟牛肉放上面'] } },
    { id:'luncheon_egg_noodles', type:'tea', name:'餐肉煎蛋麵', required:['luncheon_meat','eggs','noodles'], recipe: { ingredients:['午餐肉 2片','雞蛋 1隻','即食麵 1包'], steps:['煎香餐肉及蛋','煮熟麵條放上面'] } },
    { id:'soy_sauce_noodles', type:'tea', name:'豉油皇炒麵', required:['noodles','onion'], recipe: { ingredients:['炒麵 1個','洋蔥絲','生抽、老抽'], steps:['麵條飛水瀝乾','爆香洋蔥,加入麵條及豉油快炒'] } }
  ]);

  function getHistory() {
    try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch (e) { return []; }
  }

  function saveHistory(dishIds) {
    try {
      const hist = getHistory();
      hist.unshift({ ts: Date.now(), dish_ids: dishIds });
      localStorage.setItem(STORAGE_KEY, JSON.stringify(hist.slice(0, MAX_HISTORY)));
    } catch (e) {}
  }

  function getUsedDishIds() {
    const hist = getHistory();
    const used = new Set();
    hist.forEach(entry => { (entry.dish_ids || []).forEach(id => used.add(id)); });
    return used;
  }

  function getDiscountedIngredients() {
    const keys = Object.keys(ingredientCatalog);
    return keys.sort(() => 0.5 - Math.random()).slice(0, 2);
  }

  function scoreDish(dish, discounts) {
    let score = 10;
    let hasDiscount = false;
    dish.required.forEach(req => {
      if (discounts.includes(req)) { score += 20; hasDiscount = true; }
    });
    score += Math.random() * 5;
    return { score, hasDiscount };
  }

  const selectCity = document.getElementById('city');
  const selectMealType = document.getElementById('mealType');
  const result = document.getElementById('result');

  (function buildCityOptions() {
    Object.entries(cities).forEach(([key, city]) => {
      const op = document.createElement('option');
      op.value = key; op.textContent = city.label;
      selectCity.appendChild(op);
    });
  })();

  function render() {
    const mealType = selectMealType.value;
    const cityKey = selectCity.value;
    const currentCity = cities[cityKey];
    
    const usedIds = getUsedDishIds();
    const discounts = getDiscountedIngredients();
    
    let availableDishes = allDishes.filter(d => !usedIds.has(d.id));
    if (availableDishes.length < 5) {
      localStorage.removeItem(STORAGE_KEY);
      availableDishes = allDishes;
    }

    let selectedDishes = [];
    let soup = null;

    if (mealType === 'tea') {
      let teaOptions = availableDishes.filter(d => d.type === 'tea');
      if(teaOptions.length === 0) teaOptions = allDishes.filter(d => d.type === 'tea');
      teaOptions = teaOptions.map(d => ({ ...d, ...scoreDish(d, discounts) })).sort((a,b) => b.score - a.score);
      selectedDishes = [teaOptions[0]];
    } else {
      let mains = availableDishes.filter(d => d.type === 'main');
      let soups = availableDishes.filter(d => d.type === 'soup');
      if(mains.length < 3) mains = allDishes.filter(d => d.type === 'main');
      if(soups.length < 1) soups = allDishes.filter(d => d.type === 'soup');

      mains = mains.map(d => ({ ...d, ...scoreDish(d, discounts) })).sort((a,b) => b.score - a.score);
      soups = soups.map(d => ({ ...d, ...scoreDish(d, discounts) })).sort((a,b) => b.score - a.score);

      selectedDishes = mains.slice(0, 3);
      soup = soups[0];
    }

    const finalIds = selectedDishes.map(d => d.id);
    if (soup) finalIds.push(soup.id);
    saveHistory(finalIds);

    const colors = [...pastelColors].sort(() => Math.random() - 0.5);
    const discountLabels = discounts.map(k => ingredientCatalog[k].label).join('、');

    const generateDishHtml = (dish, idx, isSoup = false) => {
      const ytUrl = `https://www.youtube.com/results?search_query=${encodeURIComponent(dish.name + ' 食譜 教學')}`;
      const recipeId = `recipe-${dish.id}-${Date.now()}`;
      
      const ingredientsHtml = dish.recipe.ingredients.map(i => `<li>${i}</li>`).join('');
      const stepsHtml = dish.recipe.steps.map(s => `<li>${s}</li>`).join('');
      
      return `
        <li style="background-color: ${colors[idx % colors.length]}">
          <div class="dish-header">
            <strong>${isSoup ? '湯:' : (idx + 1 + '. ')}${dish.name}</strong>
            ${dish.hasDiscount ? `<span class="pill pill-discount">用咗特價食材</span>` : ''}
          </div>
          <div class="links">
            <button class="link-btn" onclick="toggleRecipe('${recipeId}')">📝 睇食譜 (食材及步驟)</button>
            <a href="${ytUrl}" target="_blank" class="link link-yt">📺 睇教學影片</a>
          </div>
          
          <div id="${recipeId}" class="recipe-content">
            <h4>🛒 準備食材:</h4>
            <ul>${ingredientsHtml}</ul>
            <h4>🍳 烹飪步驟:</h4>
            <ol>${stepsHtml}</ol>
          </div>
        </li>
      `;
    };

    let menuHtml = selectedDishes.map((dish, idx) => generateDishHtml(dish, idx)).join('');
    if (soup) { menuHtml += generateDishHtml(soup, 3, true); }

    const allReqs = new Set();
    [...selectedDishes, ...(soup ? [soup] : [])].forEach(d => {
      d.required.forEach(r => allReqs.add(r));
    });

    const shopHtml = Array.from(allReqs).map(k => {
      const isDiscount = discounts.includes(k);
      return `<span class="pill ${isDiscount ? 'pill-discount' : ''}">${ingredientCatalog[k].label} ${isDiscount ? '(特價)' : ''}</span>`;
    }).join('');
    
    const storeLinksHtml = currentCity.shops.map(shop => 
      `<a href="${shop.url}" target="_blank" class="link link-shop">🛒 去 ${shop.name} 買</a>`
    ).join('');

    result.innerHTML = `
      <div class="panel">
        <div class="badge">${mealType === 'tea' ? '港式茶餐' : '三餸一湯'}</div>
        <div class="badge badge-discount">🛒 模擬今日特價:${discountLabels}</div>
        <h2>今餐食乜餸</h2>
        <ul class="menu">${menuHtml}</ul>
        
        <div class="section">
          <h3>買餸清單 (${currentCity.label})</h3>
          <div class="pills" style="margin-bottom: 12px;">${shopHtml}</div>
          <div class="links">${storeLinksHtml}</div>
        </div>
        
        <div class="note">已嚴格過濾,保證以上菜式喺最近 10 次都未出現過。</div>
      </div>
    `;
    
    document.getElementById('status').textContent = '已生成新一餐建議!';
  }

  document.getElementById('generate').addEventListener('click', render);
  document.getElementById('reset').addEventListener('click', () => {
    localStorage.removeItem(STORAGE_KEY);
    document.getElementById('status').textContent = '已清除紀錄。';
  });

  render();
})();
</script>
</body>
</html>