const { useState, useEffect, useCallback, useMemo, useRef } = React;

/* ═══════════════════════════════════════════════════════════════
  31: [
    { name: "boneless, skinless chicken thighs", qty: 1340.0, unit: "g", cat: "Meat" },
    { name: "white miso paste", qty: 60.0, unit: "g", cat: "International" },
    { name: "mirin", qty: 45.0, unit: "g", cat: "Condiments" },
    { name: "salt", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "Black pepper", qty: 1, unit: "ct", cat: "Produce" },
    { name: "whole milk", qty: 600.0, unit: "g", cat: "Dairy" },
    { name: "white miso", qty: 60.0, unit: "g", cat: "International" },
    { name: "sharp cheddar", qty: 250.0, unit: "g", cat: "Dairy" },
    { name: "Parmigiano Reggiano", qty: 30.0, unit: "g", cat: "Pantry" },
    { name: "mozzarella", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "macaroni", qty: 672.0, unit: "g", cat: "Pasta" },
  ],
  46: [
    { name: "boneless, skinless chicken thighs", qty: 1790.0, unit: "g", cat: "Meat" },
    { name: "orange juice", qty: 240.0, unit: "g", cat: "Pantry" },
    { name: "soy sauce", qty: 90.0, unit: "g", cat: "Condiments" },
    { name: "mirin", qty: 45.0, unit: "g", cat: "Condiments" },
    { name: "chili crisp", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "onion powder", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "black pepper", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "rice", qty: 500.0, unit: "g", cat: "Pantry" },
    { name: "cornstarch", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "cold water", qty: 2.0, unit: "tbsp", cat: "Pantry" },
    { name: "orange marmalade", qty: 120.0, unit: "g", cat: "Pantry" },
    { name: "chili crisp", qty: 30.0, unit: "g", cat: "Pantry" },
    { name: "seasoned rice vinegar", qty: 30.0, unit: "g", cat: "Condiments" },
    { name: "honey", qty: 20.0, unit: "g", cat: "Pantry" },
    { name: "ginger paste or minced ginger", qty: 0.5, unit: "tbsp", cat: "Produce" },
  ],
  48: [
    { name: "Worcestershire", qty: 45.0, unit: "g", cat: "Condiments" },
    { name: "Dijon mustard", qty: 15.0, unit: "g", cat: "Condiments" },
    { name: "salt", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "onion powder", qty: 2.0, unit: "tsp", cat: "Produce" },
    { name: "black pepper", qty: 2.0, unit: "tsp", cat: "Produce" },
    { name: "dried thyme", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "nutmeg", qty: 0.125, unit: "tsp", cat: "Pantry" },
    { name: "bone broth", qty: 100.0, unit: "g", cat: "Canned" },
  ],
  49: [
    { name: "flat cut brisket or lean steak of choice", qty: 900.0, unit: "g", cat: "Meat" },
    { name: "can crushed fire- roasted tomatoes", qty: 400.0, unit: "g", cat: "Produce" },
    { name: "red enchilada sauce", qty: 120.0, unit: "g", cat: "Canned" },
    { name: "can chipotle peppers in adobo", qty: 200.0, unit: "g", cat: "Produce" },
    { name: "green chiles", qty: 80.0, unit: "g", cat: "Canned" },
    { name: "honey", qty: 60.0, unit: "g", cat: "Pantry" },
    { name: "salt", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "onion powder", qty: 2.0, unit: "tsp", cat: "Produce" },
    { name: "black pepper", qty: 2.0, unit: "tsp", cat: "Produce" },
    { name: "2% cottage cheese", qty: 800.0, unit: "g", cat: "Dairy" },
    { name: "American cheese", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "extra sharp cheddar cheese", qty: 150.0, unit: "g", cat: "Dairy" },
    { name: "red enchilada sauce", qty: 80.0, unit: "g", cat: "Canned" },
  ],
  101: [
    { name: "boneless skinless chicken thighs", qty: 24.0, unit: "oz", cat: "Meat" },
    { name: "avocado", qty: 200.0, unit: "g", cat: "Produce" },
    { name: "green enchilada sauce", qty: 50.0, unit: "g", cat: "Canned" },
    { name: "Roma tomatoes, diced", qty: 5.0, unit: "ct", cat: "Produce" },
    { name: "small white onion, diced", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "Greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "Spanish RightRice", qty: 300.0, unit: "g", cat: "Pantry" },
  ],
  143: [
    { name: "chicken breast", qty: 18.0, unit: "oz", cat: "Meat" },
    { name: "olive oil", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "Cajun seasoning", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "coarse salt", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "pepper", qty: 0.5, unit: "tbsp", cat: "Produce" },
    { name: "garlic powder", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "burrito tortillas (I used", qty: 11.0, unit: "ct", cat: "Bakery" },
    { name: "Greek yogurt", qty: 300.0, unit: "g", cat: "Dairy" },
    { name: "mozzarella", qty: 100.0, unit: "g", cat: "Dairy" },
  ],
  144: [
    { name: "chicken sausages", qty: 6.0, unit: "ct", cat: "Meat" },
    { name: "center cut bacon", qty: 6.0, unit: "ct", cat: "Meat" },
    { name: "egg whites", qty: 360.0, unit: "g", cat: "Pantry" },
    { name: "corn starch", qty: 3.0, unit: "tsp", cat: "Produce" },
    { name: "low fat cheddar", qty: 90.0, unit: "g", cat: "Dairy" },
    { name: "Salt & pepper", qty: 1, unit: "ct", cat: "Produce" },
    { name: "burrito tortillas", qty: 6.0, unit: "ct", cat: "Bakery" },
  ],
  146: [
    { name: "93% beef", qty: 1.0, unit: "lb", cat: "Meat" },
    { name: "packet of taco seasoning", qty: 1.0, unit: "ct", cat: "Pantry" },
    { name: "2% Greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "red enchilada sauce", qty: 100.0, unit: "g", cat: "Canned" },
    { name: "laughing cow cheese wedges", qty: 2.0, unit: "ct", cat: "Dairy" },
    { name: "Tbsps chopped cilantro", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "2% cheddar", qty: 60.0, unit: "g", cat: "Dairy" },
    { name: "burrito tortillas", qty: 6.0, unit: "ct", cat: "Bakery" },
  ],
  149: [
    { name: "thin sliced chicken breast", qty: 32.0, unit: "oz", cat: "Meat" },
    { name: "of cilantro, chopped", qty: 0.5, unit: "bunch", cat: "Produce" },
    { name: "avocado oil", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "honey", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "chili powder", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "paprika", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "cumin", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "coarse salt", qty: 2.0, unit: "tsp", cat: "Pantry" },
    { name: "water", qty: 0.333, unit: "cup", cat: "Pantry" },
    { name: "bell peppers", qty: 4.0, unit: "ct", cat: "Produce" },
    { name: "sweet onion", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "Salt to taste", qty: 1, unit: "ct", cat: "Pantry" },
    { name: "0% Greek yogurt", qty: 220.0, unit: "g", cat: "Dairy" },
    { name: "chipotle hot sauce", qty: 1.0, unit: "tbsp", cat: "Canned" },
    { name: "mozzarella", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "refried ranchero beans", qty: 300.0, unit: "g", cat: "Canned" },
    { name: "burrito tortillas (I used", qty: 10.0, unit: "ct", cat: "Bakery" },
  ],
  150: [
    { name: "chicken breast", qty: 32.0, unit: "oz", cat: "Meat" },
    { name: "0% greek yogurt", qty: 180.0, unit: "g", cat: "Dairy" },
    { name: "garam masala", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "Kashmiri red chili powder", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "salt", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "garlic, minced", qty: 3.0, unit: "ct", cat: "Produce" },
    { name: "Greek yogurt", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "milk", qty: 40.0, unit: "g", cat: "Dairy" },
    { name: "garlic", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "Pinch of salt", qty: 1, unit: "ct", cat: "Pantry" },
    { name: "light butter", qty: 30.0, unit: "g", cat: "Dairy" },
    { name: "onion, diced", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "Ground cumin", qty: 2.0, unit: "tsp", cat: "Pantry" },
    { name: "Kashmiri red chili powder", qty: 1.5, unit: "tsp", cat: "Pantry" },
    { name: "Coriander", qty: 1.5, unit: "tsp", cat: "Pantry" },
    { name: "Garam masala", qty: 0.75, unit: "tsp", cat: "Pantry" },
    { name: "garlic minced", qty: 6.0, unit: "ct", cat: "Produce" },
    { name: "crushed tomatoes", qty: 28.0, unit: "oz", cat: "Produce" },
    { name: "cashews", qty: 30.0, unit: "g", cat: "Pantry" },
    { name: "corn starch", qty: 0.75, unit: "tsp", cat: "Produce" },
    { name: "Salt to taste", qty: 1, unit: "ct", cat: "Pantry" },
    { name: "burrito tortillas", qty: 10.0, unit: "ct", cat: "Bakery" },
    { name: "basmati rice Cilantro", qty: 200.0, unit: "g", cat: "Produce" },
  ],
  151: [
    { name: "boneless skinless chicken thighs", qty: 24.0, unit: "oz", cat: "Meat" },
    { name: "plain Greek yogurt", qty: 300.0, unit: "g", cat: "Dairy" },
    { name: "2% cheddar", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "1/3 fat cream cheese", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "Buffalo sauce", qty: 75.0, unit: "g", cat: "Pantry" },
    { name: "honey Chopped green Onions", qty: 20.0, unit: "g", cat: "Produce" },
    { name: "Spanish RightRice", qty: 300.0, unit: "g", cat: "Pantry" },
    { name: "burrito size tortillas", qty: 10.0, unit: "ct", cat: "Bakery" },
  ],
  153: [
    { name: "boneless skinless chicken thighs", qty: 24.0, unit: "oz", cat: "Meat" },
    { name: "Seasoned evenly with salt", qty: 1, unit: "ct", cat: "Pantry" },
    { name: "Roma tomatoes , diced", qty: 5.0, unit: "ct", cat: "Produce" },
    { name: "white onion , diced", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "jalepeño pepper, diced", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "Spanish RightRice", qty: 300.0, unit: "g", cat: "Pantry" },
    { name: "Greek yogurt", qty: 300.0, unit: "g", cat: "Dairy" },
    { name: "green enchilada sauce", qty: 50.0, unit: "g", cat: "Canned" },
    { name: "2% cheddar cheese", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "burrito tortillas", qty: 12.0, unit: "ct", cat: "Bakery" },
  ],
  155: [
    { name: "boneless skinless chicken thighs", qty: 24.0, unit: "oz", cat: "Meat" },
    { name: "cottage cheese", qty: 300.0, unit: "g", cat: "Dairy" },
    { name: "white cheddar", qty: 35.0, unit: "g", cat: "Dairy" },
    { name: "cheddar powder", qty: 20.0, unit: "g", cat: "Dairy" },
    { name: "jalapeño hot sauce", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "can of green chiles", qty: 4.0, unit: "oz", cat: "Canned" },
    { name: "Basmati rice", qty: 200.0, unit: "g", cat: "Pantry" },
    { name: "burrito tortillas", qty: 9.0, unit: "ct", cat: "Bakery" },
  ],
  156: [
    { name: "eggs", qty: 4.0, unit: "ct", cat: "Pantry" },
    { name: "egg whites", qty: 150.0, unit: "g", cat: "Pantry" },
    { name: "blended cottage cheese", qty: 100.0, unit: "g", cat: "Dairy" },
    { name: "2% cheddar", qty: 40.0, unit: "g", cat: "Dairy" },
    { name: "Parmigiano Reggiano", qty: 15.0, unit: "g", cat: "Pantry" },
    { name: "corn starch", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "Salt, pepper, garlic", qty: 1, unit: "ct", cat: "Produce" },
    { name: "laughing cow cheese wedge each", qty: 0.5, unit: "ct", cat: "Dairy" },
    { name: "burrito tortillas", qty: 4.0, unit: "ct", cat: "Bakery" },
  ],
  158: [
    { name: "boneless skinless chicken thighs", qty: 30.0, unit: "oz", cat: "Meat" },
    { name: "tomato sauce", qty: 300.0, unit: "g", cat: "Produce" },
    { name: "cream cheese", qty: 60.0, unit: "g", cat: "Dairy" },
    { name: "Parmigiano Reggiano", qty: 90.0, unit: "g", cat: "Pantry" },
    { name: "burrito tortillas", qty: 6.0, unit: "ct", cat: "Bakery" },
  ],
  162: [
    { name: "skinless chicken thighs", qty: 28.0, unit: "oz", cat: "Meat" },
    { name: "orange juice", qty: 0.5, unit: "cup", cat: "Pantry" },
    { name: "Tbsps soy sauce", qty: 4.0, unit: "ct", cat: "Condiments" },
    { name: "garlic", qty: 8.0, unit: "ct", cat: "Produce" },
    { name: "cumin", qty: 2.0, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "onion", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "dried ancho chiles", qty: 3.0, unit: "ct", cat: "Pantry" },
    { name: "chipotle peppers in adobo", qty: 3.0, unit: "ct", cat: "Produce" },
    { name: "honey", qty: 20.0, unit: "g", cat: "Pantry" },
    { name: "Handful cilantro", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "frozen fries", qty: 160.0, unit: "g", cat: "Pantry" },
    { name: "Guac", qty: 75.0, unit: "g", cat: "Pantry" },
    { name: "Greek yogurt", qty: 125.0, unit: "g", cat: "Dairy" },
    { name: "burrito size tortillas of choice", qty: 5.0, unit: "ct", cat: "Bakery" },
  ],
  164: [
    { name: "skinless chicken thighs", qty: 16.0, unit: "oz", cat: "Meat" },
    { name: "packet taco seasoning", qty: 1.0, unit: "ct", cat: "Pantry" },
    { name: "2% Plain Greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "tomato puree OR crushed tomatoes", qty: 80.0, unit: "g", cat: "Produce" },
    { name: "pepperjack cheese", qty: 50.0, unit: "g", cat: "Produce" },
    { name: "laughing cow cheese wedges", qty: 2.0, unit: "ct", cat: "Dairy" },
    { name: "Tbsps chopped cilantro", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "burrito tortillas", qty: 6.0, unit: "ct", cat: "Bakery" },
  ],
  165: [
    { name: "skinless chicken thighs", qty: 24.0, unit: "oz", cat: "Meat" },
    { name: "Greek yogurt", qty: 300.0, unit: "g", cat: "Dairy" },
    { name: "pickled jalapeños", qty: 75.0, unit: "g", cat: "Produce" },
    { name: "jalapeño brine", qty: 30.0, unit: "g", cat: "Produce" },
    { name: "honey", qty: 10.0, unit: "g", cat: "Pantry" },
    { name: "2% cheddar", qty: 80.0, unit: "g", cat: "Dairy" },
    { name: "Spanish RightRice", qty: 300.0, unit: "g", cat: "Pantry" },
    { name: "burrito tortillas", qty: 10.0, unit: "ct", cat: "Bakery" },
  ],
  166: [
    { name: "large eggs", qty: 8.0, unit: "ct", cat: "Pantry" },
    { name: "egg whites", qty: 100.0, unit: "g", cat: "Pantry" },
    { name: "blended cottage cheese", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "turkey bacon", qty: 10.0, unit: "ct", cat: "Meat" },
    { name: "salt, adjust to taste", qty: 0.5, unit: "tsp", cat: "Pantry" },
    { name: "Pepper, garlic to taste", qty: 1, unit: "ct", cat: "Produce" },
    { name: "laughing cow cheese wedges", qty: 4.0, unit: "ct", cat: "Dairy" },
    { name: "bell peppers", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "onion", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "of bacon", qty: 4.0, unit: "ct", cat: "Meat" },
    { name: "light sour cream or", qty: 60.0, unit: "g", cat: "Dairy" },
    { name: "hot sauce", qty: 60.0, unit: "g", cat: "Pantry" },
    { name: "Smoked paprika, garlic, onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "burrito tortillas", qty: 6.0, unit: "ct", cat: "Bakery" },
  ],
  167: [
    { name: "93% beef", qty: 1.0, unit: "lb", cat: "Meat" },
    { name: "packet taco seasoning", qty: 1.0, unit: "ct", cat: "Pantry" },
    { name: "2% Greek yogurt", qty: 150.0, unit: "g", cat: "Dairy" },
    { name: "red enchilada sauce", qty: 60.0, unit: "g", cat: "Canned" },
    { name: "Pico", qty: 80.0, unit: "g", cat: "Pantry" },
  ],
  168: [
    { name: "orange juice", qty: 80.0, unit: "g", cat: "Pantry" },
    { name: "Tbsps soy sauce", qty: 4.0, unit: "ct", cat: "Condiments" },
    { name: "fresh garlic", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "cumin", qty: 2.0, unit: "tsp", cat: "Pantry" },
    { name: "onion", qty: 2.0, unit: "tsp", cat: "Produce" },
    { name: "paprika", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "coarse salt", qty: 2.0, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "adobo sauce", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "brown sugar", qty: 20.0, unit: "g", cat: "Pantry" },
    { name: "skinless chicken thighs", qty: 16.0, unit: "oz", cat: "Meat" },
    { name: "onion", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "of cilantro", qty: 1.0, unit: "bunch", cat: "Produce" },
    { name: "Eggs", qty: 8.0, unit: "ct", cat: "Pantry" },
    { name: "Egg whites", qty: 100.0, unit: "g", cat: "Pantry" },
    { name: "sour cream", qty: 80.0, unit: "g", cat: "Dairy" },
    { name: "burrito tortillas", qty: 5.0, unit: "ct", cat: "Bakery" },
  ],
  169: [
    { name: "onion", qty: 1.0, unit: "ct", cat: "Produce" },
    { name: "of cilantro", qty: 1.0, unit: "bunch", cat: "Produce" },
    { name: "2% cheddar", qty: 40.0, unit: "g", cat: "Dairy" },
    { name: "extra thin corn tortillas", qty: 27.0, unit: "ct", cat: "Produce" },
  ],
  170: [
    { name: "Turkey sausage", qty: 8.0, unit: "oz", cat: "Meat" },
    { name: "93% beef", qty: 8.0, unit: "oz", cat: "Meat" },
    { name: "egg whites", qty: 200.0, unit: "g", cat: "Pantry" },
    { name: "eggs", qty: 2.0, unit: "ct", cat: "Pantry" },
    { name: "blended cottage cheese", qty: 100.0, unit: "g", cat: "Dairy" },
  ],
  171: [
    { name: "whole eggs", qty: 6.0, unit: "ct", cat: "Pantry" },
    { name: "blended cottage cheese", qty: 90.0, unit: "g", cat: "Dairy" },
    { name: "reduced fat cheddar", qty: 30.0, unit: "g", cat: "Dairy" },
    { name: "parmigiano reggiano", qty: 5.0, unit: "g", cat: "Pantry" },
    { name: "English muffins", qty: 6.0, unit: "ct", cat: "Pantry" },
    { name: "Canadian bacon or ham", qty: 12.0, unit: "ct", cat: "Meat" },
    { name: "reduced fat cheddar or", qty: 6.0, unit: "ct", cat: "Dairy" },
    { name: "light Mayo", qty: 190.0, unit: "g", cat: "Condiments" },
    { name: "sriracha", qty: 20.0, unit: "g", cat: "Condiments" },
  ],
  172: [
    { name: "93% beef", qty: 32.0, unit: "oz", cat: "Meat" },
    { name: "salt", qty: 3.0, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "tsps smoked paprika", qty: 3.0, unit: "ct", cat: "Pantry" },
    { name: "tsps onion", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "tsps garlic", qty: 4.0, unit: "ct", cat: "Produce" },
    { name: "cumin", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "light Mayo", qty: 175.0, unit: "g", cat: "Condiments" },
    { name: "relish", qty: 80.0, unit: "g", cat: "Pantry" },
    { name: "white vinegar", qty: 2.0, unit: "tsp", cat: "Condiments" },
    { name: "soy sauce", qty: 75.0, unit: "tsp", cat: "Condiments" },
    { name: "of reduced fat cheddar cheese", qty: 8.0, unit: "ct", cat: "Dairy" },
    { name: "English muffins", qty: 8.0, unit: "ct", cat: "Pantry" },
  ],
  189: [
    { name: "ground chicken", qty: 1.0, unit: "lb", cat: "Meat" },
    { name: "chipotle peppers in adobo, finely chopped", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "adobo sauce", qty: 1.0, unit: "tbsp", cat: "Pantry" },
    { name: "garlic, minced", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "small onion, grated", qty: 0.5, unit: "ct", cat: "Produce" },
    { name: "salt", qty: 1.0, unit: "tsp", cat: "Pantry" },
    { name: "ground cumin", qty: 1.5, unit: "tsp", cat: "Pantry" },
    { name: "tomato paste", qty: 1.0, unit: "tbsp", cat: "Produce" },
    { name: "egg", qty: 1.0, unit: "ct", cat: "Pantry" },
    { name: "plain bread crumbs", qty: 0.25, unit: "cup", cat: "Bakery" },
  ],
  194: [
    { name: "96% beef", qty: 16.0, unit: "oz", cat: "Meat" },
    { name: "taco seasoning", qty: 30.0, unit: "g", cat: "Pantry" },
    { name: "red enchilada sauce", qty: 50.0, unit: "g", cat: "Canned" },
    { name: "pasta", qty: 200.0, unit: "g", cat: "Pasta" },
    { name: "2% cottage cheese", qty: 180.0, unit: "g", cat: "Dairy" },
    { name: "cream cheese", qty: 20.0, unit: "g", cat: "Dairy" },
    { name: "cheddar powder", qty: 16.0, unit: "g", cat: "Dairy" },
    { name: "low fat cheddar", qty: 28.0, unit: "g", cat: "Dairy" },
  ],
  195: [
    { name: "96% ground beef", qty: 454.0, unit: "g", cat: "Meat" },
    { name: "coarse salt, 1", qty: 1.5, unit: "tsp", cat: "Pantry" },
    { name: "paprika, 0.25 tsp chili powder", qty: 0.5, unit: "tsp", cat: "Pantry" },
    { name: "0% greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "red enchilada sauce", qty: 100.0, unit: "g", cat: "Canned" },
    { name: "Pico de gallo", qty: 100.0, unit: "g", cat: "Pantry" },
    { name: "2% cheddar", qty: 60.0, unit: "g", cat: "Dairy" },
    { name: "green chiles", qty: 60.0, unit: "g", cat: "Canned" },
  ],
  196: [
    { name: "96% ground chicken", qty: 454.0, unit: "g", cat: "Meat" },
    { name: "coarse salt, 1", qty: 1.5, unit: "tsp", cat: "Pantry" },
    { name: "paprika, 0.25 tsp chili powder", qty: 0.5, unit: "tsp", cat: "Pantry" },
    { name: "0% greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "red enchilada sauce", qty: 100.0, unit: "g", cat: "Canned" },
    { name: "Pico de gallo", qty: 100.0, unit: "g", cat: "Pantry" },
    { name: "2% cheddar", qty: 60.0, unit: "g", cat: "Dairy" },
    { name: "green chiles", qty: 60.0, unit: "g", cat: "Canned" },
  ],
  197: [
    { name: "andouille chicken sausage links", qty: 9.0, unit: "ct", cat: "Meat" },
    { name: "burrito tortillas", qty: 11.0, unit: "ct", cat: "Bakery" },
    { name: "egg whites", qty: 450.0, unit: "g", cat: "Pantry" },
    { name: "blended cottage cheese", qty: 90.0, unit: "g", cat: "Dairy" },
    { name: "low fat cheddar", qty: 90.0, unit: "g", cat: "Dairy" },
    { name: "corn starch", qty: 6.0, unit: "tsp", cat: "Produce" },
    { name: "Greek yogurt", qty: 200.0, unit: "g", cat: "Dairy" },
    { name: "poblano peppers", qty: 4.0, unit: "ct", cat: "Produce" },
    { name: "jalapeño peppers", qty: 2.0, unit: "ct", cat: "Produce" },
    { name: "garlic", qty: 4.0, unit: "ct", cat: "Produce" },
    { name: "lime juice", qty: 1.0, unit: "tsp", cat: "Produce" },
    { name: "onion powder", qty: 1.0, unit: "tsp", cat: "Produce" },
  ],
  {
    id: 12,
    title: "Avocado chicken rice bowls",
    book: "sc",
    page: 33,
    section: "meals",
    category: "Chicken",
    servings: 9,
    calories: 505,
    protein: 42,
    carbs: 60,
    fat: 12,
    fiber: 2,
    tags: ["rice", "slow-cooker"],
  },
  {
    id: 13,
    title: "Hawaiian BBQ Chicken Rice Bowls",
    book: "sc",
    page: 34,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 510,
    protein: 42,
    carbs: 66,
    fat: 7,
    fiber: 2,
    tags: ["bbq", "rice", "slow-cooker"],
  },
  {
    id: 14,
    title: "Spicy Steak Pasta",
    book: "sc",
    page: 35,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 480,
    protein: 37,
    carbs: 63,
    fat: 9,
    fiber: 3,
    tags: ["spicy", "pasta", "slow-cooker"],
  },
  {
    id: 15,
    title: "Chicken Tinga & Green Rice",
    book: "sc",
    page: 36,
    section: "meals",
    category: "Chicken",
    servings: 7,
    calories: 575,
    protein: 50,
    carbs: 56,
    fat: 16,
    fiber: 2,
    tags: ["rice", "slow-cooker"],
  },
  {
    id: 16,
    title: "Teriyaki Chicken Rice Bowls",
    book: "sc",
    page: 37,
    section: "meals",
    category: "Chicken",
    servings: 8,
    calories: 515,
    protein: 40,
    carbs: 65,
    fat: 9,
    fiber: 2,
    tags: ["asian", "rice", "slow-cooker"],
  },
  {
    id: 17,
    title: "Cheesy Chipotle Chicken Pasta",
    book: "sc",
    page: 38,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 460,
    protein: 40,
    carbs: 59,
    fat: 3,
    fiber: 3,
    tags: ["spicy", "pasta", "slow-cooker"],
  },
  {
    id: 18,
    title: "Lemon Harissa Chicken Pasta",
    book: "sc",
    page: 39,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 585,
    protein: 46,
    carbs: 61,
    fat: 17,
    fiber: 3,
    tags: ["curry", "pasta", "slow-cooker"],
  },
  {
    id: 19,
    title: "Honey Chipotle Chicken Burrito Bowls",
    book: "sc",
    page: 40,
    section: "meals",
    category: "Chicken",
    servings: 7,
    calories: 530,
    protein: 48,
    carbs: 59,
    fat: 10,
    fiber: 5,
    tags: ["spicy", "sweet", "mexican", "rice", "slow-cooker"],
  },
  {
    id: 20,
    title: "Chorizo Queso Mac n\u2019 Cheese",
    book: "sc",
    page: 41,
    section: "meals",
    category: "Pork",
    servings: 10,
    calories: 600,
    protein: 45,
    carbs: 58,
    fat: 3,
    fiber: 5,
    tags: ["cheesy", "mexican", "pasta", "slow-cooker"],
  },
  {
    id: 21,
    title: "Lasagna Bowls",
    book: "sc",
    page: 42,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 480,
    protein: 44,
    carbs: 48,
    fat: 14,
    fiber: 2,
    tags: ["italian", "rice", "slow-cooker"],
  },
  {
    id: 22,
    title: "Chicken Bacon Ranch Mac n\u2019 Cheese",
    book: "sc",
    page: 43,
    section: "meals",
    category: "Chicken",
    servings: 11,
    calories: 490,
    protein: 50,
    carbs: 52,
    fat: 12,
    fiber: 3,
    tags: ["cheesy", "comfort", "pasta", "slow-cooker"],
  },
  {
    id: 23,
    title: "Chimichurri Steak Rice Bowls",
    book: "sc",
    page: 44,
    section: "meals",
    category: "Beef",
    servings: 9,
    calories: 530,
    protein: 41,
    carbs: 53,
    fat: 16,
    fiber: 5,
    tags: ["mexican", "rice", "slow-cooker"],
  },
  {
    id: 24,
    title: "Buffalo Chicken & Bacon Mac n\u2019 Cheese",
    book: "sc",
    page: 45,
    section: "meals",
    category: "Chicken",
    servings: 11,
    calories: 490,
    protein: 46,
    carbs: 54,
    fat: 3,
    fiber: 3,
    tags: ["cheesy", "spicy", "comfort", "pasta", "slow-cooker"],
  },
  {
    id: 25,
    title: "Korean BBQ Beef Rice Bowls",
    book: "sc",
    page: 46,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 505,
    protein: 36,
    carbs: 56,
    fat: 14,
    fiber: 2,
    tags: ["bbq", "asian", "rice", "slow-cooker"],
  },
  {
    id: 26,
    title: "Spicy Chicken Alfredo",
    book: "sc",
    page: 47,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 525,
    protein: 46,
    carbs: 58,
    fat: 3,
    fiber: 3,
    tags: ["spicy", "italian", "slow-cooker"],
  },
  {
    id: 27,
    title: "Slow Cooker Chili",
    book: "sc",
    page: 48,
    section: "meals",
    category: "Mixed",
    servings: 8,
    calories: 445,
    protein: 44,
    carbs: 26,
    fat: 3,
    fiber: 6,
    tags: ["chili", "slow-cooker"],
  },
  {
    id: 28,
    title: "Chili Mac",
    book: "sc",
    page: 49,
    section: "meals",
    category: "Mixed",
    servings: 12,
    calories: 560,
    protein: 44,
    carbs: 60,
    fat: 18,
    fiber: 6,
    tags: ["cheesy", "chili", "pasta", "slow-cooker"],
  },
  {
    id: 29,
    title: "Butter Chicken Chili",
    book: "sc",
    page: 50,
    section: "meals",
    category: "Chicken",
    servings: 7,
    calories: 390,
    protein: 47,
    carbs: 11,
    fat: 17,
    fiber: 6,
    tags: ["chili", "slow-cooker"],
  },
  {
    id: 30,
    title: "Frozen Cheesy Buffalo Chicken Burritos",
    book: "sc",
    page: 51,
    section: "meals",
    category: "Chicken",
    servings: 14,
    calories: 450,
    protein: 37,
    carbs: 40,
    fat: 3,
    fiber: 5,
    tags: ["spicy", "mexican", "slow-cooker"],
  },
  {
    id: 31,
    title: "miso Mac",
    book: "sc",
    page: 52,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 600,
    protein: 47,
    carbs: 61,
    fat: 18,
    fiber: 3,
    tags: ["cheesy", "asian", "pasta", "slow-cooker"],
  },
  {
    id: 32,
    title: "Cheesy Southwestern Chicken Orzo",
    book: "sc",
    page: 53,
    section: "meals",
    category: "Chicken",
    servings: 12,
    calories: 545,
    protein: 42,
    carbs: 58,
    fat: 15,
    fiber: 5,
    tags: ["mexican", "pasta", "slow-cooker"],
  },
  {
    id: 33,
    title: "Jalape\u00f1o popper Mac n cheese",
    book: "sc",
    page: 54,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 570,
    protein: 54,
    carbs: 61,
    fat: 3,
    fiber: 3,
    tags: ["cheesy", "spicy", "pasta", "slow-cooker"],
  },
  {
    id: 34,
    title: "Honey Harissa chicken Rice bowls",
    book: "sc",
    page: 55,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 565,
    protein: 49,
    carbs: 57,
    fat: 14,
    fiber: 2,
    tags: ["sweet", "curry", "rice", "slow-cooker"],
  },
  {
    id: 35,
    title: "Buffalo Chicken Mac N Cheese",
    book: "sc",
    page: 56,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 480,
    protein: 35,
    carbs: 59,
    fat: 3,
    fiber: 3,
    tags: ["cheesy", "spicy", "pasta", "slow-cooker"],
  },
  {
    id: 36,
    title: "Chicken Enchilada Soup",
    book: "sc",
    page: 57,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 480,
    protein: 45,
    carbs: 24,
    fat: 3,
    fiber: 6,
    tags: ["mexican", "soup", "slow-cooker"],
  },
  {
    id: 37,
    title: "Pasta Bolognese",
    book: "sc",
    page: 58,
    section: "meals",
    category: "Beef",
    servings: 12,
    calories: 525,
    protein: 40,
    carbs: 58,
    fat: 15,
    fiber: 3,
    tags: ["italian", "pasta", "slow-cooker"],
  },
  {
    id: 38,
    title: "carnitas inspired Pulled Pork Burritos",
    book: "sc",
    page: 59,
    section: "meals",
    category: "Pork",
    servings: 15,
    calories: 510,
    protein: 35,
    carbs: 53,
    fat: 17,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 39,
    title: "Thai red Curry Peanut Chicken Bowls",
    book: "sc",
    page: 60,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 595,
    protein: 48,
    carbs: 60,
    fat: 16,
    fiber: 2,
    tags: ["curry", "rice", "peanut", "slow-cooker"],
  },
  {
    id: 40,
    title: "Chicken Parmesan Pasta",
    book: "sc",
    page: 61,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 475,
    protein: 43,
    carbs: 58,
    fat: 3,
    fiber: 3,
    tags: ["italian", "pasta", "slow-cooker"],
  },
  {
    id: 41,
    title: "Street taco chicken & chipotle lime rice",
    book: "sc",
    page: 62,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 510,
    protein: 46,
    carbs: 57,
    fat: 9,
    fiber: 5,
    tags: ["spicy", "mexican", "rice", "slow-cooker"],
  },
  {
    id: 42,
    title: "Cheesy Chili Crisp Miso Orzo",
    book: "sc",
    page: 63,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 540,
    protein: 42,
    carbs: 60,
    fat: 13,
    fiber: 3,
    tags: ["asian", "pasta", "slow-cooker"],
  },
  {
    id: 43,
    title: "Korean Beef Noodles",
    book: "sc",
    page: 64,
    section: "meals",
    category: "Beef",
    servings: 7,
    calories: 500,
    protein: 36,
    carbs: 61,
    fat: 13,
    fiber: 3,
    tags: ["asian", "pasta", "slow-cooker"],
  },
  {
    id: 44,
    title: "Red enchilada chicken soup",
    book: "sc",
    page: 65,
    section: "meals",
    category: "Chicken",
    servings: 8,
    calories: 445,
    protein: 54,
    carbs: 18,
    fat: 15,
    fiber: 6,
    tags: ["mexican", "soup", "slow-cooker"],
  },
  {
    id: 45,
    title: "Japanese curry",
    book: "sc",
    page: 66,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 405,
    protein: 37,
    carbs: 38,
    fat: 11,
    fiber: 3,
    tags: ["asian", "curry", "slow-cooker"],
  },
  {
    id: 46,
    title: "Chili Crisp Orange Chicken Rice Bowls",
    book: "sc",
    page: 67,
    section: "meals",
    category: "Chicken",
    servings: 10,
    calories: 510,
    protein: 40,
    carbs: 59,
    fat: 10,
    fiber: 2,
    tags: ["asian", "rice", "slow-cooker"],
  },
  {
    id: 47,
    title: "Meat Lovers Pizza Mac n\u2019 Cheese",
    book: "sc",
    page: 68,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 610,
    protein: 55,
    carbs: 57,
    fat: 21,
    fiber: 3,
    tags: ["cheesy", "pasta", "pizza", "slow-cooker"],
  },
  {
    id: 48,
    title: "Caramelized onion beef Burritos",
    book: "sc",
    page: 69,
    section: "meals",
    category: "Beef",
    servings: 11,
    calories: 535,
    protein: 42,
    carbs: 54,
    fat: 16,
    fiber: 5,
    tags: ["mexican", "savory", "slow-cooker"],
  },
  {
    id: 49,
    title: "Brisket Queso fries",
    book: "sc",
    page: 70,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 660,
    protein: 41,
    carbs: 65,
    fat: 26,
    fiber: 5,
    tags: ["cheesy", "mexican", "slow-cooker"],
  },
  {
    id: 50,
    title: "Quesabirria Burritos",
    book: "sc",
    page: 71,
    section: "meals",
    category: "Beef",
    servings: 18,
    calories: 510,
    protein: 40,
    carbs: 52,
    fat: 14,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 51,
    title: "Italian beef Sandwiches",
    book: "sc",
    page: 72,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 670,
    protein: 50,
    carbs: 55,
    fat: 27,
    fiber: 3,
    tags: ["italian", "handheld", "slow-cooker"],
  },
  {
    id: 52,
    title: "Chili Con Carne",
    book: "sc",
    page: 73,
    section: "meals",
    category: "Mixed",
    servings: 10,
    calories: 500,
    protein: 58,
    carbs: 12,
    fat: 23,
    fiber: 6,
    tags: ["chili", "slow-cooker"],
  },
  {
    id: 53,
    title: "Hot Honey Brisket Ragu Pasta",
    book: "sc",
    page: 74,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 517,
    protein: 43,
    carbs: 63,
    fat: 11,
    fiber: 3,
    tags: ["spicy", "sweet", "pasta", "slow-cooker"],
  },
  {
    id: 54,
    title: "Chipotle brisket breakfast burritos",
    book: "sc",
    page: 75,
    section: "meals",
    category: "Beef",
    servings: 12,
    calories: 505,
    protein: 48,
    carbs: 40,
    fat: 17,
    fiber: 5,
    tags: ["spicy", "mexican", "breakfast", "slow-cooker"],
  },
  {
    id: 55,
    title: "Beef Stroganoff orzo",
    book: "sc",
    page: 76,
    section: "meals",
    category: "Beef",
    servings: 12,
    calories: 550,
    protein: 51,
    carbs: 60,
    fat: 13,
    fiber: 3,
    tags: ["pasta", "slow-cooker"],
  },
  {
    id: 56,
    title: "BBQ beef loaded sweet potatoes",
    book: "sc",
    page: 77,
    section: "meals",
    category: "Beef",
    servings: 10,
    calories: 555,
    protein: 51,
    carbs: 52,
    fat: 17,
    fiber: 3,
    tags: ["bbq", "comfort", "slow-cooker"],
  },
  {
    id: 57,
    title: "French Onion Pasta",
    book: "sc",
    page: 78,
    section: "meals",
    category: "Mixed",
    servings: 12,
    calories: 540,
    protein: 51,
    carbs: 52,
    fat: 14,
    fiber: 3,
    tags: ["pasta", "savory", "slow-cooker"],
  },
  {
    id: 58,
    title: "Cheesesteak Pasta",
    book: "sc",
    page: 79,
    section: "meals",
    category: "Beef",
    servings: 12,
    calories: 520,
    protein: 48,
    carbs: 49,
    fat: 15,
    fiber: 3,
    tags: ["cheesy", "pasta", "slow-cooker"],
  },
  {
    id: 59,
    title: "Buffalo Chicken Bacon Ranch Rice Bowls",
    book: "sc",
    page: 80,
    section: "meals",
    category: "Chicken",
    servings: 7,
    calories: 570,
    protein: 56,
    carbs: 35,
    fat: 20,
    fiber: 2,
    tags: ["spicy", "comfort", "rice", "slow-cooker"],
  },
  {
    id: 60,
    title: "Broccoli Cheddar Soup Mac n\u2019 Cheese",
    book: "sc",
    page: 81,
    section: "meals",
    category: "Mixed",
    servings: 12,
    calories: 550,
    protein: 46,
    carbs: 51,
    fat: 18,
    fiber: 6,
    tags: ["cheesy", "soup", "pasta", "slow-cooker"],
  },
  {
    id: 61,
    title: "Chicken Tinga",
    book: "sc",
    page: 83,
    section: "proteins",
    category: "Chicken",
    servings: 10,
    calories: 180,
    protein: 24,
    carbs: 5,
    fat: 7,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 62,
    title: "Chili Crisp Orange Chicken",
    book: "sc",
    page: 84,
    section: "proteins",
    category: "Chicken",
    servings: 16,
    calories: 200,
    protein: 22,
    carbs: 10,
    fat: 6,
    fiber: 1,
    tags: ["asian", "slow-cooker"],
  },
  {
    id: 63,
    title: "beef Birria",
    book: "sc",
    page: 85,
    section: "proteins",
    category: "Beef",
    servings: 16,
    calories: 175,
    protein: 28,
    carbs: 1,
    fat: 6,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 64,
    title: "Peanut Miso Chicken",
    book: "sc",
    page: 86,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 210,
    protein: 26,
    carbs: 7,
    fat: 9,
    fiber: 1,
    tags: ["asian", "peanut", "slow-cooker"],
  },
  {
    id: 65,
    title: "Honey Harissa chicken",
    book: "sc",
    page: 87,
    section: "proteins",
    category: "Chicken",
    servings: 16,
    calories: 160,
    protein: 23,
    carbs: 4,
    fat: 5,
    fiber: 1,
    tags: ["sweet", "curry", "slow-cooker"],
  },
  {
    id: 66,
    title: "Chimichurri Steak",
    book: "sc",
    page: 88,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 210,
    protein: 25,
    carbs: 3,
    fat: 11,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 67,
    title: "Green Enchilada Chicken",
    book: "sc",
    page: 89,
    section: "proteins",
    category: "Chicken",
    servings: 10,
    calories: 175,
    protein: 22,
    carbs: 6,
    fat: 5,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 68,
    title: "Butter Chicken",
    book: "sc",
    page: 90,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 260,
    protein: 34,
    carbs: 7,
    fat: 11,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 69,
    title: "Honey Chipotle Chicken",
    book: "sc",
    page: 91,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 175,
    protein: 23,
    carbs: 8,
    fat: 6,
    fiber: 1,
    tags: ["spicy", "sweet", "slow-cooker"],
  },
  {
    id: 70,
    title: "Carnitas Inspired Pulled Pork",
    book: "sc",
    page: 92,
    section: "proteins",
    category: "Pork",
    servings: 16,
    calories: 215,
    protein: 24,
    carbs: 5,
    fat: 10,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 71,
    title: "Tzatziki chicken",
    book: "sc",
    page: 93,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 170,
    protein: 27,
    carbs: 4,
    fat: 6,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 72,
    title: "Pepper steak",
    book: "sc",
    page: 94,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 175,
    protein: 26,
    carbs: 4,
    fat: 7,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 73,
    title: "Thai red Curry Peanut Chicken",
    book: "sc",
    page: 95,
    section: "proteins",
    category: "Chicken",
    servings: 16,
    calories: 180,
    protein: 24,
    carbs: 5,
    fat: 7,
    fiber: 1,
    tags: ["curry", "peanut", "slow-cooker"],
  },
  {
    id: 74,
    title: "Caramelized onion beef",
    book: "sc",
    page: 96,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 200,
    protein: 28,
    carbs: 1,
    fat: 9,
    fiber: 1,
    tags: ["savory", "slow-cooker"],
  },
  {
    id: 75,
    title: "Street taco chicken",
    book: "sc",
    page: 97,
    section: "proteins",
    category: "Chicken",
    servings: 16,
    calories: 165,
    protein: 23,
    carbs: 6,
    fat: 5,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 76,
    title: "Hot Honey Brisket Ragu",
    book: "sc",
    page: 98,
    section: "proteins",
    category: "Beef",
    servings: 10,
    calories: 275,
    protein: 34,
    carbs: 13,
    fat: 10,
    fiber: 1,
    tags: ["spicy", "sweet", "slow-cooker"],
  },
  {
    id: 77,
    title: "Garlic Rosemary Chicken",
    book: "sc",
    page: 99,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 150,
    protein: 23,
    carbs: 1,
    fat: 5,
    fiber: 1,
    tags: ["comfort", "slow-cooker"],
  },
  {
    id: 78,
    title: "Italian beef",
    book: "sc",
    page: 100,
    section: "proteins",
    category: "Beef",
    servings: 11,
    calories: 220,
    protein: 24,
    carbs: 4,
    fat: 12,
    fiber: 1,
    tags: ["italian", "slow-cooker"],
  },
  {
    id: 79,
    title: "Hawaiian BBQ Chicken",
    book: "sc",
    page: 101,
    section: "proteins",
    category: "Chicken",
    servings: 10,
    calories: 165,
    protein: 23,
    carbs: 6,
    fat: 5,
    fiber: 1,
    tags: ["bbq", "slow-cooker"],
  },
  {
    id: 80,
    title: "beef Bolognese",
    book: "sc",
    page: 102,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 260,
    protein: 28,
    carbs: 10,
    fat: 12,
    fiber: 1,
    tags: ["italian", "slow-cooker"],
  },
  {
    id: 81,
    title: "Chili Crisp Miso Chicken",
    book: "sc",
    page: 103,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 185,
    protein: 23,
    carbs: 6,
    fat: 6,
    fiber: 1,
    tags: ["asian", "slow-cooker"],
  },
  {
    id: 82,
    title: "Honey Chipotle Beef",
    book: "sc",
    page: 105,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 180,
    protein: 25,
    carbs: 5,
    fat: 7,
    fiber: 1,
    tags: ["spicy", "sweet", "slow-cooker"],
  },
  {
    id: 83,
    title: "Red enchilada chicken",
    book: "sc",
    page: 106,
    section: "proteins",
    category: "Chicken",
    servings: 16,
    calories: 170,
    protein: 22,
    carbs: 7,
    fat: 5,
    fiber: 5,
    tags: ["mexican", "slow-cooker"],
  },
  {
    id: 84,
    title: "Korean BBQ Beef",
    book: "sc",
    page: 107,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 295,
    protein: 30,
    carbs: 12,
    fat: 14,
    fiber: 1,
    tags: ["bbq", "asian", "slow-cooker"],
  },
  {
    id: 85,
    title: "Pineapple teriyaki pulled pork",
    book: "sc",
    page: 108,
    section: "proteins",
    category: "Pork",
    servings: 16,
    calories: 230,
    protein: 24,
    carbs: 11,
    fat: 9,
    fiber: 1,
    tags: ["asian", "slow-cooker"],
  },
  {
    id: 86,
    title: "Dr Pepper BBQ Beef",
    book: "sc",
    page: 109,
    section: "proteins",
    category: "Beef",
    servings: 16,
    calories: 170,
    protein: 25,
    carbs: 3,
    fat: 7,
    fiber: 1,
    tags: ["bbq", "slow-cooker"],
  },
  {
    id: 87,
    title: "Honey Teriyaki Chicken",
    book: "sc",
    page: 110,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 175,
    protein: 23,
    carbs: 10,
    fat: 5,
    fiber: 1,
    tags: ["sweet", "asian", "slow-cooker"],
  },
  {
    id: 88,
    title: "Buffalo Ranch Chicken",
    book: "sc",
    page: 111,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 210,
    protein: 24,
    carbs: 5,
    fat: 8,
    fiber: 1,
    tags: ["spicy", "comfort", "slow-cooker"],
  },
  {
    id: 89,
    title: "Chicken Fried Rice",
    book: "mp",
    page: 16,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 41,
    carbs: 47,
    fat: 17,
    fiber: 2,
    tags: ["rice", "meal-prep"],
  },
  {
    id: 90,
    title: "Garlic Parmesan Chicken Alfredo",
    book: "mp",
    page: 17,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 44,
    carbs: 52,
    fat: 15,
    fiber: 3,
    tags: ["italian", "meal-prep"],
  },
  {
    id: 91,
    title: "Philly Cheesesteak Pasta",
    book: "mp",
    page: 18,
    section: "500cal",
    category: "Beef",
    servings: 6,
    calories: 510,
    protein: 42,
    carbs: 58,
    fat: 13,
    fiber: 3,
    tags: ["cheesy", "pasta", "meal-prep"],
  },
  {
    id: 92,
    title: "Spicy Peanut Sauce Noodles",
    book: "mp",
    page: 19,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 42,
    carbs: 60,
    fat: 13,
    fiber: 3,
    tags: ["spicy", "pasta", "peanut", "meal-prep"],
  },
  {
    id: 93,
    title: "Queso Chicken & Rice",
    book: "mp",
    page: 20,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 515,
    protein: 48,
    carbs: 51,
    fat: 13,
    fiber: 5,
    tags: ["cheesy", "mexican", "rice", "meal-prep"],
  },
  {
    id: 94,
    title: "Cajun Chicken Mac N Cheese",
    book: "mp",
    page: 21,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 45,
    carbs: 54,
    fat: 13,
    fiber: 3,
    tags: ["cheesy", "cajun", "pasta", "meal-prep"],
  },
  {
    id: 95,
    title: "Chicken Bacon Ranch Fried Rice",
    book: "mp",
    page: 22,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 42,
    carbs: 49,
    fat: 17,
    fiber: 2,
    tags: ["comfort", "rice", "meal-prep"],
  },
  {
    id: 96,
    title: "Orzo Burrito Bowls",
    book: "mp",
    page: 23,
    section: "500cal",
    category: "Mixed",
    servings: 6,
    calories: 520,
    protein: 41,
    carbs: 61,
    fat: 12,
    fiber: 5,
    tags: ["mexican", "rice", "pasta", "meal-prep"],
  },
  {
    id: 97,
    title: "Lemon Parmesan Chicken & Rice",
    book: "mp",
    page: 24,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 495,
    protein: 40,
    carbs: 52,
    fat: 15,
    fiber: 2,
    tags: ["italian", "rice", "meal-prep"],
  },
  {
    id: 98,
    title: "Orange Chicken",
    book: "mp",
    page: 25,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 510,
    protein: 40,
    carbs: 58,
    fat: 13,
    fiber: 3,
    tags: ["asian", "meal-prep"],
  },
  {
    id: 99,
    title: "Chipotle Chicken & Bacon Alfredo",
    book: "mp",
    page: 26,
    section: "500cal",
    category: "Chicken",
    servings: 4,
    calories: 535,
    protein: 51,
    carbs: 62,
    fat: 12,
    fiber: 3,
    tags: ["spicy", "italian", "comfort", "meal-prep"],
  },
  {
    id: 100,
    title: "Loaded Cheddar Fries",
    book: "mp",
    page: 27,
    section: "500cal",
    category: "Mixed",
    servings: 6,
    calories: 500,
    protein: 41,
    carbs: 42,
    fat: 19,
    fiber: 3,
    tags: ["cheesy", "comfort", "meal-prep"],
  },
  {
    id: 101,
    title: "Chicken:",
    book: "mp",
    page: 28,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 45,
    carbs: 44,
    fat: 16,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 102,
    title: "Taco Mac N Cheese",
    book: "mp",
    page: 29,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 41,
    carbs: 62,
    fat: 12,
    fiber: 5,
    tags: ["cheesy", "mexican", "pasta", "meal-prep"],
  },
  {
    id: 103,
    title: "Honey Garlic Chicken & Rice",
    book: "mp",
    page: 30,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 495,
    protein: 37,
    carbs: 59,
    fat: 13,
    fiber: 2,
    tags: ["sweet", "rice", "meal-prep"],
  },
  {
    id: 104,
    title: "Korean Beef Rice Bowls",
    book: "mp",
    page: 31,
    section: "500cal",
    category: "Beef",
    servings: 5,
    calories: 500,
    protein: 34,
    carbs: 53,
    fat: 16,
    fiber: 2,
    tags: ["asian", "rice", "meal-prep"],
  },
  {
    id: 105,
    title: "Rigatoni Alla Vodka",
    book: "mp",
    page: 32,
    section: "500cal",
    category: "Mixed",
    servings: 6,
    calories: 515,
    protein: 43,
    carbs: 60,
    fat: 10,
    fiber: 3,
    tags: ["pasta", "meal-prep"],
  },
  {
    id: 106,
    title: "Green Chili Queso Mac N Cheese",
    book: "mp",
    page: 33,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 47,
    carbs: 60,
    fat: 12,
    fiber: 6,
    tags: ["cheesy", "mexican", "chili", "pasta", "meal-prep"],
  },
  {
    id: 107,
    title: "Pepperoni Pizza Bagel Bites",
    book: "mp",
    page: 34,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 515,
    protein: 30,
    carbs: 65,
    fat: 17,
    fiber: 3,
    tags: ["pizza", "handheld", "meal-prep"],
  },
  {
    id: 108,
    title: "Creamy Chicken Pesto Pasta",
    book: "mp",
    page: 35,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 51,
    carbs: 42,
    fat: 18,
    fiber: 3,
    tags: ["pasta", "meal-prep"],
  },
  {
    id: 109,
    title: "Honey Garlic Chili Crisp Noodles",
    book: "mp",
    page: 36,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 505,
    protein: 40,
    carbs: 63,
    fat: 11,
    fiber: 3,
    tags: ["sweet", "pasta", "meal-prep"],
  },
  {
    id: 110,
    title: "Buffalo Chicken Mac N Cheese",
    book: "mp",
    page: 37,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 39,
    carbs: 59,
    fat: 14,
    fiber: 3,
    tags: ["cheesy", "spicy", "pasta", "meal-prep"],
  },
  {
    id: 111,
    title: "Italian Meatball Pasta",
    book: "mp",
    page: 38,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 45,
    carbs: 60,
    fat: 11,
    fiber: 3,
    tags: ["italian", "pasta", "meal-prep"],
  },
  {
    id: 112,
    title: "Pepperoni Pizza Mac N Cheese",
    book: "mp",
    page: 39,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 505,
    protein: 35,
    carbs: 59,
    fat: 18,
    fiber: 3,
    tags: ["cheesy", "pasta", "pizza", "meal-prep"],
  },
  {
    id: 113,
    title: "Butter Chicken Rice Bowls",
    book: "mp",
    page: 40,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 510,
    protein: 44,
    carbs: 54,
    fat: 13,
    fiber: 2,
    tags: ["rice", "meal-prep"],
  },
  {
    id: 114,
    title: "Everything Bagel Chicken Alfredo",
    book: "mp",
    page: 41,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 510,
    protein: 51,
    carbs: 54,
    fat: 12,
    fiber: 3,
    tags: ["italian", "handheld", "meal-prep"],
  },
  {
    id: 115,
    title: "Pollo Asado Burrito Bowls",
    book: "mp",
    page: 42,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 50,
    carbs: 52,
    fat: 10,
    fiber: 5,
    tags: ["mexican", "rice", "meal-prep"],
  },
  {
    id: 116,
    title: "Chicken Bulgogi Rice Bowls",
    book: "mp",
    page: 43,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 495,
    protein: 41,
    carbs: 53,
    fat: 13,
    fiber: 2,
    tags: ["asian", "rice", "meal-prep"],
  },
  {
    id: 117,
    title: "Halal Cart-Style Chicken & Rice",
    book: "mp",
    page: 44,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 43,
    carbs: 50,
    fat: 15,
    fiber: 2,
    tags: ["rice", "meal-prep"],
  },
  {
    id: 118,
    title: "Jalape\u00f1o Popper Mac N Cheese",
    book: "mp",
    page: 45,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 40,
    carbs: 65,
    fat: 12,
    fiber: 3,
    tags: ["cheesy", "spicy", "pasta", "meal-prep"],
  },
  {
    id: 119,
    title: "Roasted Garlic Poblano Chicken & Rice",
    book: "mp",
    page: 46,
    section: "500cal",
    category: "Chicken",
    servings: 7,
    calories: 515,
    protein: 52,
    carbs: 47,
    fat: 0,
    fiber: 2,
    tags: ["rice", "meal-prep"],
  },
  {
    id: 120,
    title: "White Cheddar Truffle Mac",
    book: "mp",
    page: 47,
    section: "500cal",
    category: "Mixed",
    servings: 6,
    calories: 500,
    protein: 38,
    carbs: 61,
    fat: 10,
    fiber: 3,
    tags: ["cheesy", "pasta", "meal-prep"],
  },
  {
    id: 121,
    title: "Cajun Chicken Alfredo",
    book: "mp",
    page: 48,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 515,
    protein: 46,
    carbs: 45,
    fat: 18,
    fiber: 3,
    tags: ["cajun", "italian", "meal-prep"],
  },
  {
    id: 122,
    title: "Southwest Chicken & Veggie Orzo",
    book: "mp",
    page: 49,
    section: "500cal",
    category: "Chicken",
    servings: 6,
    calories: 510,
    protein: 41,
    carbs: 59,
    fat: 13,
    fiber: 3,
    tags: ["breakfast", "pasta", "meal-prep"],
  },
  {
    id: 123,
    title: "Chorizo Mac N Cheese",
    book: "mp",
    page: 50,
    section: "500cal",
    category: "Pork",
    servings: 5,
    calories: 500,
    protein: 39,
    carbs: 56,
    fat: 13,
    fiber: 3,
    tags: ["cheesy", "pasta", "meal-prep"],
  },
  {
    id: 124,
    title: "Cube one butternut squash, weigh out 500g. Add 10g of o",
    book: "mp",
    page: 51,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 510,
    protein: 39,
    carbs: 68,
    fat: 11,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 125,
    title: "Spicy Tomato Chicken Pasta",
    book: "mp",
    page: 52,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 36,
    carbs: 56,
    fat: 3,
    fiber: 3,
    tags: ["spicy", "pasta", "meal-prep"],
  },
  {
    id: 126,
    title: "Breakfast Mac N Cheese",
    book: "mp",
    page: 53,
    section: "500cal",
    category: "Mixed",
    servings: 6,
    calories: 500,
    protein: 51,
    carbs: 37,
    fat: 20,
    fiber: 3,
    tags: ["cheesy", "breakfast", "pasta", "meal-prep"],
  },
  {
    id: 127,
    title: "Chimichurri Pasta W/ Bbq Chicken",
    book: "mp",
    page: 54,
    section: "500cal",
    category: "Chicken",
    servings: 6,
    calories: 510,
    protein: 38,
    carbs: 53,
    fat: 17,
    fiber: 5,
    tags: ["bbq", "mexican", "pasta", "meal-prep"],
  },
  {
    id: 128,
    title: "Spicy Sausage Pasta",
    book: "mp",
    page: 55,
    section: "500cal",
    category: "Pork",
    servings: 5,
    calories: 505,
    protein: 40,
    carbs: 61,
    fat: 15,
    fiber: 3,
    tags: ["spicy", "pasta", "meal-prep"],
  },
  {
    id: 129,
    title: "Honey Habanero Chicken And Rice",
    book: "mp",
    page: 56,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 39,
    carbs: 54,
    fat: 15,
    fiber: 2,
    tags: ["spicy", "sweet", "rice", "meal-prep"],
  },
  {
    id: 130,
    title: "Honey Bbq Chicken Mac N Cheese",
    book: "mp",
    page: 57,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 510,
    protein: 43,
    carbs: 59,
    fat: 12,
    fiber: 3,
    tags: ["cheesy", "sweet", "bbq", "pasta", "meal-prep"],
  },
  {
    id: 131,
    title: "Korean Beef & Cheesy Gochujang Pasta",
    book: "mp",
    page: 58,
    section: "500cal",
    category: "Beef",
    servings: 5,
    calories: 515,
    protein: 38,
    carbs: 62,
    fat: 13,
    fiber: 3,
    tags: ["asian", "pasta", "meal-prep"],
  },
  {
    id: 132,
    title: "Creamy Roasted Red Pepper Pasta",
    book: "mp",
    page: 59,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 500,
    protein: 40,
    carbs: 63,
    fat: 11,
    fiber: 3,
    tags: ["pasta", "meal-prep"],
  },
  {
    id: 133,
    title: "Creamy Southwestern Chicken & Rice",
    book: "mp",
    page: 60,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 505,
    protein: 41,
    carbs: 58,
    fat: 13,
    fiber: 5,
    tags: ["mexican", "rice", "meal-prep"],
  },
  {
    id: 134,
    title: "Spicy Taco Bowls",
    book: "mp",
    page: 61,
    section: "500cal",
    category: "Mixed",
    servings: 5,
    calories: 515,
    protein: 44,
    carbs: 53,
    fat: 14,
    fiber: 5,
    tags: ["spicy", "mexican", "rice", "meal-prep"],
  },
  {
    id: 135,
    title: "Creamy Chipotle Chicken Pasta",
    book: "mp",
    page: 62,
    section: "500cal",
    category: "Chicken",
    servings: 8,
    calories: 495,
    protein: 44,
    carbs: 56,
    fat: 14,
    fiber: 3,
    tags: ["spicy", "pasta", "meal-prep"],
  },
  {
    id: 136,
    title: "Creamy Fajita Chicken Orzo",
    book: "mp",
    page: 63,
    section: "500cal",
    category: "Chicken",
    servings: 6,
    calories: 500,
    protein: 48,
    carbs: 58,
    fat: 0,
    fiber: 5,
    tags: ["mexican", "pasta", "meal-prep"],
  },
  {
    id: 137,
    title: "Teriyaki Chicken Fried Rice",
    book: "mp",
    page: 64,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 515,
    protein: 40,
    carbs: 57,
    fat: 14,
    fiber: 2,
    tags: ["asian", "rice", "meal-prep"],
  },
  {
    id: 138,
    title: "Creamy Cajun Chicken & Rice",
    book: "mp",
    page: 65,
    section: "500cal",
    category: "Chicken",
    servings: 5,
    calories: 500,
    protein: 43,
    carbs: 59,
    fat: 9,
    fiber: 2,
    tags: ["cajun", "rice", "meal-prep"],
  },
  {
    id: 139,
    title: "Honey Chipotle Steak Bowls",
    book: "mp",
    page: 66,
    section: "500cal",
    category: "Beef",
    servings: 5,
    calories: 490,
    protein: 40,
    carbs: 49,
    fat: 15,
    fiber: 2,
    tags: ["spicy", "sweet", "rice", "meal-prep"],
  },
  {
    id: 140,
    title: "Tikka Masala Burritos",
    book: "mp",
    page: 69,
    section: "burritos",
    category: "Chicken",
    servings: 11,
    calories: 500,
    protein: 41,
    carbs: 47,
    fat: 17,
    fiber: 5,
    tags: ["mexican", "curry", "meal-prep"],
  },
  {
    id: 141,
    title: "Cheeseburger Burritos",
    book: "mp",
    page: 70,
    section: "burritos",
    category: "Beef",
    servings: 14,
    calories: 430,
    protein: 38,
    carbs: 39,
    fat: 13,
    fiber: 5,
    tags: ["cheesy", "mexican", "meal-prep"],
  },
  {
    id: 142,
    title: "Korean Bbq Bulgogi Burritos",
    book: "mp",
    page: 71,
    section: "burritos",
    category: "Mixed",
    servings: 11,
    calories: 515,
    protein: 40,
    carbs: 61,
    fat: 11,
    fiber: 5,
    tags: ["bbq", "asian", "mexican", "meal-prep"],
  },
  {
    id: 143,
    title: "Chicken/Marinade:",
    book: "mp",
    page: 72,
    section: "burritos",
    category: "Chicken",
    servings: 11,
    calories: 500,
    protein: 36,
    carbs: 48,
    fat: 13,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 144,
    title: "Protein:",
    book: "mp",
    page: 73,
    section: "burritos",
    category: "Mixed",
    servings: 6,
    calories: 435,
    protein: 44,
    carbs: 30,
    fat: 17,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 145,
    title: "Cheesesteak Burritos",
    book: "mp",
    page: 74,
    section: "burritos",
    category: "Beef",
    servings: 10,
    calories: 390,
    protein: 34,
    carbs: 29,
    fat: 13,
    fiber: 5,
    tags: ["cheesy", "mexican", "meal-prep"],
  },
  {
    id: 146,
    title: "Taco Beef:",
    book: "mp",
    page: 75,
    section: "burritos",
    category: "Beef",
    servings: 6,
    calories: 365,
    protein: 28,
    carbs: 35,
    fat: 13,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 147,
    title: "Chorizo Breakfast Burritos",
    book: "mp",
    page: 76,
    section: "burritos",
    category: "Pork",
    servings: 14,
    calories: 410,
    protein: 38,
    carbs: 23,
    fat: 16,
    fiber: 5,
    tags: ["mexican", "breakfast", "meal-prep"],
  },
  {
    id: 148,
    title: "Quesabirria Burritos",
    book: "mp",
    page: 77,
    section: "burritos",
    category: "Beef",
    servings: 18,
    calories: 510,
    protein: 40,
    carbs: 52,
    fat: 14,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 149,
    title: "Fajita Chicken/Marinade:",
    book: "mp",
    page: 78,
    section: "burritos",
    category: "Chicken",
    servings: 10,
    calories: 390,
    protein: 36,
    carbs: 34,
    fat: 12,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 150,
    title: "Chicken/Marinade:",
    book: "mp",
    page: 79,
    section: "burritos",
    category: "Chicken",
    servings: 10,
    calories: 390,
    protein: 31,
    carbs: 45,
    fat: 8,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 151,
    title: "Cheesy Buffalo Chicken:",
    book: "mp",
    page: 80,
    section: "burritos",
    category: "Chicken",
    servings: 10,
    calories: 470,
    protein: 32,
    carbs: 56,
    fat: 3,
    fiber: 3,
    tags: ["spicy", "meal-prep"],
  },
  {
    id: 152,
    title: "Sweet Chili Chicken Burritos",
    book: "mp",
    page: 81,
    section: "burritos",
    category: "Chicken",
    servings: 10,
    calories: 510,
    protein: 37,
    carbs: 63,
    fat: 11,
    fiber: 6,
    tags: ["mexican", "chili", "meal-prep"],
  },
  {
    id: 153,
    title: "Chicken/Seasoning:",
    book: "mp",
    page: 82,
    section: "burritos",
    category: "Chicken",
    servings: 12,
    calories: 440,
    protein: 30,
    carbs: 53,
    fat: 12,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 154,
    title: "Carne Asada Mac N Cheese Burritos",
    book: "mp",
    page: 83,
    section: "burritos",
    category: "Mixed",
    servings: 14,
    calories: 545,
    protein: 45,
    carbs: 55,
    fat: 15,
    fiber: 5,
    tags: ["cheesy", "mexican", "pasta", "meal-prep"],
  },
  {
    id: 155,
    title: "Chicken:",
    book: "mp",
    page: 84,
    section: "burritos",
    category: "Chicken",
    servings: 9,
    calories: 445,
    protein: 32,
    carbs: 54,
    fat: 11,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 156,
    title: "Cheesy Eggs:",
    book: "mp",
    page: 85,
    section: "burritos",
    category: "Mixed",
    servings: 4,
    calories: 420,
    protein: 40,
    carbs: 28,
    fat: 15,
    fiber: 3,
    tags: ["breakfast", "meal-prep"],
  },
  {
    id: 157,
    title: "Southwestern Breakfast Burritos",
    book: "mp",
    page: 86,
    section: "burritos",
    category: "Mixed",
    servings: 11,
    calories: 520,
    protein: 43,
    carbs: 43,
    fat: 19,
    fiber: 5,
    tags: ["mexican", "breakfast", "meal-prep"],
  },
  {
    id: 158,
    title: "Chicken:",
    book: "mp",
    page: 87,
    section: "burritos",
    category: "Chicken",
    servings: 6,
    calories: 470,
    protein: 44,
    carbs: 39,
    fat: 16,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 159,
    title: "Pizza Rolls",
    book: "mp",
    page: 88,
    section: "burritos",
    category: "Mixed",
    servings: 8,
    calories: 335,
    protein: 28,
    carbs: 28,
    fat: 11,
    fiber: 3,
    tags: ["pizza", "handheld", "meal-prep"],
  },
  {
    id: 160,
    title: "Garlic Parm Alfredo Pizza Rolls",
    book: "mp",
    page: 89,
    section: "burritos",
    category: "Mixed",
    servings: 8,
    calories: 340,
    protein: 34,
    carbs: 26,
    fat: 10,
    fiber: 3,
    tags: ["italian", "pizza", "handheld", "meal-prep"],
  },
  {
    id: 161,
    title: "Queso Chicken Rolls",
    book: "mp",
    page: 90,
    section: "burritos",
    category: "Chicken",
    servings: 8,
    calories: 335,
    protein: 35,
    carbs: 25,
    fat: 9,
    fiber: 5,
    tags: ["cheesy", "mexican", "handheld", "meal-prep"],
  },
  {
    id: 162,
    title: "Pollo Asado:",
    book: "mp",
    page: 91,
    section: "burritos",
    category: "Chicken",
    servings: 5,
    calories: 510,
    protein: 47,
    carbs: 48,
    fat: 15,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 163,
    title: "Buffalo Chicken Dip Burrito",
    book: "mp",
    page: 92,
    section: "burritos",
    category: "Chicken",
    servings: 14,
    calories: 450,
    protein: 37,
    carbs: 40,
    fat: 16,
    fiber: 5,
    tags: ["spicy", "mexican", "meal-prep"],
  },
  {
    id: 164,
    title: "Chicken:",
    book: "mp",
    page: 93,
    section: "burritos",
    category: "Chicken",
    servings: 6,
    calories: 350,
    protein: 27,
    carbs: 35,
    fat: 12,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 165,
    title: "Chicken:",
    book: "mp",
    page: 94,
    section: "burritos",
    category: "Chicken",
    servings: 10,
    calories: 440,
    protein: 38,
    carbs: 44,
    fat: 12,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 166,
    title: "Turkey Bacon Frittata:",
    book: "mp",
    page: 95,
    section: "burritos",
    category: "Turkey",
    servings: 6,
    calories: 410,
    protein: 32,
    carbs: 34,
    fat: 16,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 167,
    title: "Taco Beef:",
    book: "mp",
    page: 96,
    section: "burritos",
    category: "Beef",
    servings: 16,
    calories: 170,
    protein: 15,
    carbs: 13,
    fat: 7,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 168,
    title: "Street Taco Chicken:",
    book: "mp",
    page: 97,
    section: "burritos",
    category: "Chicken",
    servings: 5,
    calories: 575,
    protein: 48,
    carbs: 54,
    fat: 18,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 169,
    title: "Street Taco Toppings:",
    book: "mp",
    page: 98,
    section: "burritos",
    category: "Mixed",
    servings: 27,
    calories: 90,
    protein: 9,
    carbs: 10,
    fat: 1,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 170,
    title: "Sausage:",
    book: "mp",
    page: 99,
    section: "burritos",
    category: "Pork",
    servings: 6,
    calories: 385,
    protein: 33,
    carbs: 29,
    fat: 15,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 171,
    title: "Sheet Pan Eggs:",
    book: "mp",
    page: 100,
    section: "burritos",
    category: "Mixed",
    servings: 6,
    calories: 405,
    protein: 31,
    carbs: 27,
    fat: 18,
    fiber: 3,
    tags: ["breakfast", "meal-prep"],
  },
  {
    id: 172,
    title: "Burger Beef:",
    book: "mp",
    page: 101,
    section: "burritos",
    category: "Beef",
    servings: 8,
    calories: 390,
    protein: 29,
    carbs: 31,
    fat: 16,
    fiber: 3,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 173,
    title: "Smashburger Hot Pockets",
    book: "mp",
    page: 102,
    section: "burritos",
    category: "Mixed",
    servings: 8,
    calories: 275,
    protein: 18,
    carbs: 40,
    fat: 3,
    fiber: 3,
    tags: ["spicy", "handheld", "meal-prep"],
  },
  {
    id: 174,
    title: "Pollo Asado (Air Fried)",
    book: "mp",
    page: 104,
    section: "proteins",
    category: "Chicken",
    servings: 7,
    calories: 150,
    protein: 23,
    carbs: 5,
    fat: 5,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 175,
    title: "Garlic Parmesan Chicken",
    book: "mp",
    page: 105,
    section: "proteins",
    category: "Chicken",
    servings: 4,
    calories: 180,
    protein: 24,
    carbs: 2,
    fat: 8,
    fiber: 1,
    tags: ["italian", "meal-prep"],
  },
  {
    id: 176,
    title: "Beef Bulgogi",
    book: "mp",
    page: 106,
    section: "proteins",
    category: "Beef",
    servings: 12,
    calories: 195,
    protein: 26,
    carbs: 9,
    fat: 6,
    fiber: 1,
    tags: ["asian", "meal-prep"],
  },
  {
    id: 177,
    title: "Carne Asada",
    book: "mp",
    page: 107,
    section: "proteins",
    category: "Mixed",
    servings: 16,
    calories: 195,
    protein: 25,
    carbs: 5,
    fat: 8,
    fiber: 1,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 178,
    title: "Sweet Chili Chicken",
    book: "mp",
    page: 108,
    section: "proteins",
    category: "Chicken",
    servings: 12,
    calories: 175,
    protein: 23,
    carbs: 8,
    fat: 5,
    fiber: 6,
    tags: ["chili", "meal-prep"],
  },
  {
    id: 179,
    title: "Italian Meatballs (And Marinara)",
    book: "mp",
    page: 109,
    section: "proteins",
    category: "Mixed",
    servings: 20,
    calories: 55,
    protein: 8,
    carbs: 1,
    fat: 2,
    fiber: 1,
    tags: ["italian", "meal-prep"],
  },
  {
    id: 180,
    title: "Teriyaki Chicken",
    book: "mp",
    page: 110,
    section: "proteins",
    category: "Chicken",
    servings: 5,
    calories: 195,
    protein: 24,
    carbs: 8,
    fat: 7,
    fiber: 1,
    tags: ["asian", "meal-prep"],
  },
  {
    id: 181,
    title: "Street Taco Chicken",
    book: "mp",
    page: 111,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 150,
    protein: 25,
    carbs: 4,
    fat: 3,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 182,
    title: "Sweet & Spicy Korean Beef",
    book: "mp",
    page: 112,
    section: "proteins",
    category: "Beef",
    servings: 6,
    calories: 195,
    protein: 26,
    carbs: 9,
    fat: 6,
    fiber: 1,
    tags: ["spicy", "asian", "meal-prep"],
  },
  {
    id: 183,
    title: "Chimichurri Chicken Bites",
    book: "mp",
    page: 113,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 184,
    protein: 26,
    carbs: 0,
    fat: 8,
    fiber: 5,
    tags: ["mexican", "meal-prep"],
  },
  {
    id: 184,
    title: "Chipotle-Style Chicken",
    book: "mp",
    page: 114,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 130,
    protein: 22,
    carbs: 0,
    fat: 5,
    fiber: 1,
    tags: ["spicy", "meal-prep"],
  },
  {
    id: 185,
    title: "Chipotle-Style Steak",
    book: "mp",
    page: 115,
    section: "proteins",
    category: "Beef",
    servings: 6,
    calories: 200,
    protein: 24,
    carbs: 0,
    fat: 11,
    fiber: 1,
    tags: ["spicy", "meal-prep"],
  },
  {
    id: 186,
    title: "Butter Chicken",
    book: "mp",
    page: 116,
    section: "proteins",
    category: "Chicken",
    servings: 5,
    calories: 305,
    protein: 39,
    carbs: 14,
    fat: 9,
    fiber: 1,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 187,
    title: "Lemon Pepper Chicken",
    book: "mp",
    page: 117,
    section: "proteins",
    category: "Chicken",
    servings: 6,
    calories: 145,
    protein: 26,
    carbs: 0,
    fat: 4,
    fiber: 1,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 188,
    title: "Chicken Bulgogi",
    book: "mp",
    page: 118,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 190,
    protein: 23,
    carbs: 11,
    fat: 7,
    fiber: 1,
    tags: ["asian", "meal-prep"],
  },
  {
    id: 189,
    title: "1 Lb Ground Chicken",
    book: "mp",
    page: 119,
    section: "proteins",
    category: "Chicken",
    servings: 15,
    calories: 45,
    protein: 8,
    carbs: 2,
    fat: 1,
    fiber: 1,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 190,
    title: "Grilled Buffalo Chicken Strips",
    book: "mp",
    page: 120,
    section: "proteins",
    category: "Chicken",
    servings: 9,
    calories: 170,
    protein: 24,
    carbs: 0,
    fat: 11,
    fiber: 1,
    tags: ["spicy", "meal-prep"],
  },
  {
    id: 191,
    title: "Italian Style Chicken Nuggets",
    book: "mp",
    page: 121,
    section: "proteins",
    category: "Chicken",
    servings: 5,
    calories: 200,
    protein: 24,
    carbs: 3,
    fat: 10,
    fiber: 1,
    tags: ["italian", "meal-prep"],
  },
  {
    id: 192,
    title: "Honey Garlic Chicken",
    book: "mp",
    page: 122,
    section: "proteins",
    category: "Chicken",
    servings: 7,
    calories: 155,
    protein: 23,
    carbs: 7,
    fat: 5,
    fiber: 1,
    tags: ["sweet", "meal-prep"],
  },
  {
    id: 193,
    title: "Halal Cart-Style Chicken",
    book: "mp",
    page: 123,
    section: "proteins",
    category: "Chicken",
    servings: 8,
    calories: 170,
    protein: 26,
    carbs: 1,
    fat: 8,
    fiber: 1,
    tags: ["comfort", "meal-prep"],
  },
  {
    id: 194,
    title: "Taco Mac",
    book: "mp",
    page: 128,
    section: "counter",
    category: "Mixed",
    servings: 5,
    calories: 335,
    protein: 31,
    carbs: 35,
    fat: 8,
    fiber: 5,
    tags: ["cheesy", "mexican", "pasta", "meal-prep"],
  },
  {
    id: 195,
    title: "Beefy Queso Burritos",
    book: "mp",
    page: 129,
    section: "counter",
    category: "Beef",
    servings: 8,
    calories: 330,
    protein: 31,
    carbs: 33,
    fat: 9,
    fiber: 5,
    tags: ["cheesy", "mexican", "meal-prep"],
  },
  {
    id: 196,
    title: "Chicken Queso Burritos",
    book: "mp",
    page: 130,
    section: "counter",
    category: "Chicken",
    servings: 8,
    calories: 320,
    protein: 32,
    carbs: 33,
    fat: 8,
    fiber: 5,
    tags: ["cheesy", "mexican", "meal-prep"],
  },
  {
    id: 197,
    title: "Breakfast Burritos",
    book: "mp",
    page: 131,
    section: "counter",
    category: "Mixed",
    servings: 11,
    calories: 335,
    protein: 30,
    carbs: 34,
    fat: 12,
    fiber: 5,
    tags: ["mexican", "breakfast", "meal-prep"],
  },
  {
    id: 198,
    title: "Breakfast Bowls",
    book: "mp",
    page: 132,
    section: "counter",
    category: "Mixed",
    servings: 6,
    calories: 435,
    protein: 43,
    carbs: 38,
    fat: 13,
    fiber: 2,
    tags: ["breakfast", "rice", "meal-prep"],
  },
];

const ALL_SECTIONS = [...new Set(RECIPE_DB.map((r) => r.section))].sort();

const ALL_TAGS = [...new Set(RECIPE_DB.flatMap((r) => r.tags))].sort();
const CATEGORIES = [...new Set(RECIPE_DB.map((r) => r.category))].sort();

// ─────────────────────────────────────────────────────────────────────────
// UNIT CONVERSION MATRIX
// ─────────────────────────────────────────────────────────────────────────
// Maps units to conversion factors. Structure: UNIT_CONV[from][to] = factor
// Used by the Recipe Detail alt-units column and Shopping List scaling logic.
// Units with empty conversion objects (ct, bag, bunch, head) are count-based
// and have no metric/imperial equivalent — they remain as-is in conversions.
// ─────────────────────────────────────────────────────────────────────────
const UNIT_CONV = {
  tsp: { tbsp: 1 / 3, cup: 1 / 48, ml: 4.929, fl_oz: 0.1667, oz: 0.1667, g: 5 },
  tbsp: { tsp: 3, cup: 1 / 16, ml: 14.787, fl_oz: 0.5, oz: 0.5, g: 14.2 },
  cup: { tbsp: 16, tsp: 48, ml: 236.6, fl_oz: 8, oz: 8, g: 240, l: 0.2366 },
  oz: { g: 28.35, lb: 1 / 16, cup: 0.125, ml: 29.57 },
  lb: { oz: 16, g: 453.6, kg: 0.4536 },
  ml: { l: 0.001, fl_oz: 0.03381, cup: 0.00423, tsp: 0.2029, tbsp: 0.0676 },
  l: { ml: 1000, cup: 4.227, fl_oz: 33.814, g: 1000 },
  g: { oz: 0.03527, lb: 0.002205, kg: 0.001 },
  kg: { g: 1000, lb: 2.205, oz: 35.27 },
  can: { oz: 15, g: 425, ml: 444 },
  ct: {},
  bag: {},
  bunch: {},
  head: {},
};

function convertUnit(amt, from, to) {
  if (from === to) return amt;
  if (UNIT_CONV[from] && UNIT_CONV[from][to] !== undefined)
    return amt * UNIT_CONV[from][to];
  return null;
}

function fmtAmt(n) {
  if (n === 0) return "0";
  if (n >= 100) return Math.round(n).toString();
  if (n >= 10) return (Math.round(n * 10) / 10).toString();
  const f = n % 1;
  const w = Math.floor(n);
  const fracs = [
    [0.125, "⅛"],
    [0.25, "¼"],
    [0.333, "⅓"],
    [0.375, "⅜"],
    [0.5, "½"],
    [0.625, "⅝"],
    [0.667, "⅔"],
    [0.75, "¾"],
    [0.875, "⅞"],
  ];
  for (const [v, s] of fracs) {
    if (Math.abs(f - v) < 0.04) return w > 0 ? `${w} ${s}` : s;
  }
  return (Math.round(n * 100) / 100).toString();
}

// ── STORE LOCATIONS ────────────────────────────────────────────
const STORE_LOCATIONS = [
  {
    id: "kroger_ft",
    name: "Kroger Finneytown",
    chain: "kroger",
    area: "Finneytown",
  },
  {
    id: "kroger_mm",
    name: "Kroger Mason Montgomery",
    chain: "kroger",
    area: "Mason",
  },
  {
    id: "sams_tc",
    name: "Sam's Club Tri County",
    chain: "sams",
    area: "Tri County",
  },
  {
    id: "sams_wo",
    name: "Sam's Club White Oak",
    chain: "sams",
    area: "White Oak",
  },
];

// ── STORE PRICE ESTIMATES ──────────────────────────────────────
// Per-category average price per unit by store chain
const STORE_PRICES = {
  Meat: {
    kroger_ft: 4.99,
    kroger_mm: 5.29,
    sams_tc: 3.89,
    sams_wo: 3.89,
    unit: "/lb",
  },
  Produce: {
    kroger_ft: 1.29,
    kroger_mm: 1.39,
    sams_tc: 0.99,
    sams_wo: 0.99,
    unit: "/ct",
  },
  Canned: {
    kroger_ft: 1.49,
    kroger_mm: 1.49,
    sams_tc: 1.09,
    sams_wo: 1.09,
    unit: "/can",
  },
  Dairy: {
    kroger_ft: 3.49,
    kroger_mm: 3.69,
    sams_tc: 2.89,
    sams_wo: 2.89,
    unit: "/unit",
  },
  Pantry: {
    kroger_ft: 2.99,
    kroger_mm: 2.99,
    sams_tc: 4.49,
    sams_wo: 4.49,
    unit: "/unit",
  },
  Condiments: {
    kroger_ft: 3.49,
    kroger_mm: 3.49,
    sams_tc: 5.29,
    sams_wo: 5.29,
    unit: "/unit",
  },
  Bakery: {
    kroger_ft: 3.29,
    kroger_mm: 3.49,
    sams_tc: 4.99,
    sams_wo: 4.99,
    unit: "/pkg",
  },
  Pasta: {
    kroger_ft: 1.69,
    kroger_mm: 1.69,
    sams_tc: 2.49,
    sams_wo: 2.49,
    unit: "/lb",
  },
  Frozen: {
    kroger_ft: 3.49,
    kroger_mm: 3.49,
    sams_tc: 5.99,
    sams_wo: 5.99,
    unit: "/bag",
  },
  International: {
    kroger_ft: 2.99,
    kroger_mm: 3.29,
    sams_tc: 4.79,
    sams_wo: 4.79,
    unit: "/unit",
  },
  Seafood: {
    kroger_ft: 7.99,
    kroger_mm: 8.49,
    sams_tc: 6.49,
    sams_wo: 6.49,
    unit: "/lb",
  },
  Snacks: {
    kroger_ft: 3.99,
    kroger_mm: 3.99,
    sams_tc: 6.49,
    sams_wo: 6.49,
    unit: "/bag",
  },
  Deli: {
    kroger_ft: 5.99,
    kroger_mm: 6.29,
    sams_tc: 8.99,
    sams_wo: 8.99,
    unit: "/ct",
  },
};

// ── INGREDIENT DATABASE ────────────────────────────────────────
// Per-recipe ingredient lists for recipes that have been parsed.
// Key: recipe ID. Each ingredient: { name, qty, unit, cat (store category) }
// Recipes without an entry here fall back to recipe-level shopping view.
const INGREDIENT_DB = {
  // id:1 — Queso Chicken Mac n' Cheese (SC p.22, 10 srv)
  1: [
    { name: "chicken breast", qty: 3, unit: "lb", cat: "Meat" },
    { name: "elbow macaroni", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "Velveeta cheese", qty: 16, unit: "oz", cat: "Dairy" },
    { name: "Rotel diced tomatoes", qty: 2, unit: "can", cat: "Canned" },
    { name: "cream of chicken soup", qty: 1, unit: "can", cat: "Canned" },
    { name: "chicken broth", qty: 2, unit: "cup", cat: "Canned" },
    { name: "shredded cheddar", qty: 8, unit: "oz", cat: "Dairy" },
    { name: "cream cheese", qty: 4, unit: "oz", cat: "Dairy" },
    { name: "garlic powder", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "onion powder", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 0.5, unit: "tsp", cat: "Pantry" },
  ],
  // id:5 — Frozen Cheesy Chicken & Rice Burritos (SC p.26, 18 srv)
  5: [
    { name: "chicken breast", qty: 4, unit: "lb", cat: "Meat" },
    { name: "white rice", qty: 3, unit: "cup", cat: "Pantry" },
    { name: "shredded cheddar", qty: 16, unit: "oz", cat: "Dairy" },
    { name: "Rotel diced tomatoes", qty: 2, unit: "can", cat: "Canned" },
    { name: "cream of chicken soup", qty: 2, unit: "can", cat: "Canned" },
    { name: "chicken broth", qty: 2, unit: "cup", cat: "Canned" },
    { name: "large flour tortillas", qty: 18, unit: "ct", cat: "Bakery" },
    { name: "taco seasoning", qty: 2, unit: "tbsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "cumin", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
  ],
  // id:7 — Garlic Butter Chicken Alfredo (SC p.28, 10 srv)
  7: [
    { name: "chicken breast", qty: 3, unit: "lb", cat: "Meat" },
    { name: "fettuccine pasta", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "heavy cream", qty: 2, unit: "cup", cat: "Dairy" },
    { name: "parmesan cheese", qty: 6, unit: "oz", cat: "Dairy" },
    { name: "butter", qty: 4, unit: "tbsp", cat: "Dairy" },
    { name: "garlic cloves", qty: 8, unit: "ct", cat: "Produce" },
    { name: "chicken broth", qty: 1, unit: "cup", cat: "Canned" },
    { name: "Italian seasoning", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 0.5, unit: "tsp", cat: "Pantry" },
    { name: "parsley flakes", qty: 1, unit: "tbsp", cat: "Pantry" },
  ],
  // id:25 — Korean BBQ Beef Rice Bowls (SC p.46, 10 srv)
  25: [
    { name: "beef chuck roast", qty: 3, unit: "lb", cat: "Meat" },
    { name: "white rice", qty: 3, unit: "cup", cat: "Pantry" },
    { name: "soy sauce", qty: 0.5, unit: "cup", cat: "Condiments" },
    { name: "brown sugar", qty: 3, unit: "tbsp", cat: "Pantry" },
    { name: "sesame oil", qty: 2, unit: "tbsp", cat: "International" },
    { name: "rice vinegar", qty: 2, unit: "tbsp", cat: "International" },
    { name: "gochujang paste", qty: 2, unit: "tbsp", cat: "International" },
    { name: "garlic cloves", qty: 6, unit: "ct", cat: "Produce" },
    { name: "fresh ginger", qty: 1, unit: "tbsp", cat: "Produce" },
    { name: "green onions", qty: 4, unit: "ct", cat: "Produce" },
    { name: "sesame seeds", qty: 1, unit: "tbsp", cat: "International" },
  ],
  // id:28 — Chili Mac (SC p.49, 12 srv)
  28: [
    { name: "ground beef 90/10", qty: 2.5, unit: "lb", cat: "Meat" },
    { name: "elbow macaroni", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "kidney beans", qty: 2, unit: "can", cat: "Canned" },
    { name: "diced tomatoes", qty: 2, unit: "can", cat: "Canned" },
    { name: "tomato paste", qty: 1, unit: "can", cat: "Canned" },
    { name: "beef broth", qty: 2, unit: "cup", cat: "Canned" },
    { name: "shredded cheddar", qty: 8, unit: "oz", cat: "Dairy" },
    { name: "chili powder", qty: 3, unit: "tbsp", cat: "Pantry" },
    { name: "cumin", qty: 1, unit: "tbsp", cat: "Pantry" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "garlic cloves", qty: 4, unit: "ct", cat: "Produce" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
  ],
  // id:37 — Pasta Bolognese (SC p.58, 12 srv)
  37: [
    { name: "ground beef 90/10", qty: 2, unit: "lb", cat: "Meat" },
    { name: "Italian sausage", qty: 1, unit: "lb", cat: "Meat" },
    { name: "spaghetti", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "crushed tomatoes", qty: 28, unit: "oz", cat: "Canned" },
    { name: "tomato paste", qty: 1, unit: "can", cat: "Canned" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "carrots", qty: 2, unit: "ct", cat: "Produce" },
    { name: "celery stalks", qty: 2, unit: "ct", cat: "Produce" },
    { name: "garlic cloves", qty: 6, unit: "ct", cat: "Produce" },
    { name: "Italian seasoning", qty: 2, unit: "tsp", cat: "Pantry" },
    { name: "red wine", qty: 0.5, unit: "cup", cat: "Condiments" },
    { name: "parmesan cheese", qty: 4, unit: "oz", cat: "Dairy" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
  ],
  // id:47 — Meat Lovers Pizza Mac n' Cheese (SC p.68, 10 srv)
  47: [
    { name: "ground beef 90/10", qty: 1, unit: "lb", cat: "Meat" },
    { name: "Italian sausage", qty: 1, unit: "lb", cat: "Meat" },
    { name: "pepperoni slices", qty: 4, unit: "oz", cat: "Deli" },
    { name: "elbow macaroni", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "mozzarella cheese", qty: 8, unit: "oz", cat: "Dairy" },
    { name: "parmesan cheese", qty: 4, unit: "oz", cat: "Dairy" },
    { name: "marinara sauce", qty: 24, unit: "oz", cat: "Canned" },
    { name: "cream cheese", qty: 4, unit: "oz", cat: "Dairy" },
    { name: "Italian seasoning", qty: 2, unit: "tsp", cat: "Pantry" },
    { name: "garlic powder", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 0.5, unit: "tsp", cat: "Pantry" },
  ],
  // id:50 — Quesabirria Burritos (SC p.71, 18 srv)
  50: [
    { name: "beef chuck roast", qty: 4, unit: "lb", cat: "Meat" },
    { name: "large flour tortillas", qty: 18, unit: "ct", cat: "Bakery" },
    { name: "shredded Oaxaca cheese", qty: 16, unit: "oz", cat: "Dairy" },
    { name: "dried guajillo chiles", qty: 6, unit: "ct", cat: "International" },
    { name: "dried ancho chiles", qty: 3, unit: "ct", cat: "International" },
    { name: "fire roasted tomatoes", qty: 1, unit: "can", cat: "Canned" },
    { name: "beef broth", qty: 3, unit: "cup", cat: "Canned" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "garlic cloves", qty: 6, unit: "ct", cat: "Produce" },
    { name: "cumin", qty: 1, unit: "tbsp", cat: "Pantry" },
    { name: "oregano", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "apple cider vinegar", qty: 2, unit: "tbsp", cat: "Condiments" },
  ],
  // id:52 — Chili Con Carne (SC p.73, 10 srv)
  52: [
    { name: "ground beef 90/10", qty: 2, unit: "lb", cat: "Meat" },
    { name: "ground turkey 93/7", qty: 1, unit: "lb", cat: "Meat" },
    { name: "kidney beans", qty: 2, unit: "can", cat: "Canned" },
    { name: "black beans", qty: 1, unit: "can", cat: "Canned" },
    { name: "diced tomatoes", qty: 2, unit: "can", cat: "Canned" },
    { name: "tomato paste", qty: 1, unit: "can", cat: "Canned" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "bell pepper", qty: 1, unit: "ct", cat: "Produce" },
    { name: "garlic cloves", qty: 4, unit: "ct", cat: "Produce" },
    { name: "chili powder", qty: 3, unit: "tbsp", cat: "Pantry" },
    { name: "cumin", qty: 2, unit: "tsp", cat: "Pantry" },
    { name: "smoked paprika", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
  ],
  // id:55 — Beef Stroganoff Orzo (SC p.76, 12 srv)
  55: [
    { name: "beef stew meat", qty: 2.5, unit: "lb", cat: "Meat" },
    { name: "orzo pasta", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "cream of mushroom soup", qty: 2, unit: "can", cat: "Canned" },
    { name: "beef broth", qty: 2, unit: "cup", cat: "Canned" },
    { name: "sour cream", qty: 8, unit: "oz", cat: "Dairy" },
    { name: "mushrooms", qty: 8, unit: "oz", cat: "Produce" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "garlic cloves", qty: 4, unit: "ct", cat: "Produce" },
    { name: "Worcestershire sauce", qty: 2, unit: "tbsp", cat: "Condiments" },
    { name: "Dijon mustard", qty: 1, unit: "tbsp", cat: "Condiments" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 0.5, unit: "tsp", cat: "Pantry" },
  ],
  // id:57 — French Onion Pasta (SC p.78, 12 srv)
  57: [
    { name: "ground beef 90/10", qty: 2, unit: "lb", cat: "Meat" },
    { name: "penne pasta", qty: 1, unit: "lb", cat: "Pasta" },
    { name: "yellow onions", qty: 4, unit: "ct", cat: "Produce" },
    { name: "beef broth", qty: 3, unit: "cup", cat: "Canned" },
    { name: "gruyere cheese", qty: 8, unit: "oz", cat: "Dairy" },
    { name: "provolone cheese", qty: 6, unit: "oz", cat: "Dairy" },
    { name: "butter", qty: 3, unit: "tbsp", cat: "Dairy" },
    { name: "Worcestershire sauce", qty: 2, unit: "tbsp", cat: "Condiments" },
    { name: "thyme", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "garlic cloves", qty: 4, unit: "ct", cat: "Produce" },
    { name: "salt", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "black pepper", qty: 0.5, unit: "tsp", cat: "Pantry" },
  ],
  // id:70 — Carnitas Inspired Pulled Pork (SC p.91, 16 srv)
  70: [
    { name: "pork shoulder/butt", qty: 5, unit: "lb", cat: "Meat" },
    { name: "orange juice", qty: 1, unit: "cup", cat: "Produce" },
    { name: "lime juice", qty: 2, unit: "tbsp", cat: "Produce" },
    { name: "garlic cloves", qty: 6, unit: "ct", cat: "Produce" },
    { name: "cumin", qty: 1, unit: "tbsp", cat: "Pantry" },
    { name: "oregano", qty: 2, unit: "tsp", cat: "Pantry" },
    { name: "chili powder", qty: 1, unit: "tbsp", cat: "Pantry" },
    { name: "smoked paprika", qty: 1, unit: "tsp", cat: "Pantry" },
    { name: "onion", qty: 1, unit: "ct", cat: "Produce" },
    { name: "bay leaves", qty: 2, unit: "ct", cat: "Pantry" },
    { name: "salt", qty: 2, unit: "tsp", cat: "Pantry" },
  ],
};

// Helper: get unit conversions for display
function getAltUnits(qty, unit) {
  const alts = [];
  const convs = UNIT_CONV[unit];
  if (!convs) return alts;
  const show = {
    oz: ["g", "ml"],
    lb: ["g", "kg"],
    cup: ["ml", "tbsp"],
    tbsp: ["tsp", "ml"],
    tsp: ["ml", "tbsp"],
    g: ["oz", "lb"],
    ml: ["fl_oz", "cup"],
  };
  const targets = show[unit] || Object.keys(convs).slice(0, 2);
  for (const t of targets) {
    if (convs[t]) alts.push({ qty: qty * convs[t], unit: t });
  }
  return alts;
}

// Helper: estimate cost for an ingredient at a store location
function ingredientCost(ingredient, storeId) {
  const cat = ingredient.cat || "Pantry";
  const priceData = STORE_PRICES[cat];
  if (!priceData || priceData[storeId] === undefined) return null;
  const basePrice = priceData[storeId];
  // Normalize qty to the store's unit basis for rough estimate
  const q = ingredient.qty || 1;
  if (
    ingredient.unit === "can" ||
    ingredient.unit === "ct" ||
    ingredient.unit === "bag"
  )
    return basePrice * q;
  if (ingredient.unit === "lb") return basePrice * q;
  if (ingredient.unit === "oz") return basePrice * (q / 16);
  if (ingredient.unit === "cup") return basePrice * (q * 0.5);
  if (ingredient.unit === "tbsp") return basePrice * (q * 0.03);
  if (ingredient.unit === "tsp") return basePrice * (q * 0.01);
  return basePrice;
}

// ─────────────────────────────────────────────────────────────────────────
// GLOBAL STYLESHEET
// ─────────────────────────────────────────────────────────────────────────
// Single injected <style> block. All design tokens live in :root as CSS vars
// (--bg, --accent, --green, etc.) so theming changes are one-variable edits.
// Typography uses DM Sans (body) + Playfair Display (display) loaded from
// Google Fonts. No Tailwind — all classes are hand-rolled utility classes.
//
// Responsive breakpoint: 640px (single-column layouts below).
//
// Key class families:
//   .nav*           — top navigation bar
//   .card           — base card primitive (used by recipes, stats, etc)
//   .recipe-card    — recipe grid card (title → macros → meta hierarchy)
//   .macro-lg       — tier-2 macro badges (prominent, color-coded)
//   .plan-*         — meal plan grid cells
//   .sv-*           — Source Viewer overlay (modal)
//   .toast          — transient notifications (top-center, 2.8s auto-dismiss)
// ─────────────────────────────────────────────────────────────────────────
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,500;0,9..40,700;1,9..40,400&family=Playfair+Display:wght@600;800&display=swap');

:root {
  --bg: #0f1117;
  --bg2: #181b24;
  --bg3: #1e2230;
  --surface: #252937;
  --border: #2e3347;
  --text: #e8eaf0;
  --text2: #9498ab;
  --accent: #6c63ff;
  --accent2: #8b83ff;
  --accent-glow: rgba(108,99,255,0.15);
  --green: #3ecf8e;
  --red: #ff6b6b;
  --orange: #ffb347;
  --yellow: #ffd43b;
  --cyan: #22d3ee;
  --kroger: #0268c8;
  --sams: #0060a9;
  --radius: 12px;
  --shadow: 0 4px 24px rgba(0,0,0,0.3);
}

* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'DM Sans',sans-serif; background:var(--bg); color:var(--text); }

.app { display:flex; flex-direction:column; min-height:100vh; max-width:100vw; overflow-x:hidden; }

/* ── NAV ── */
.nav { display:flex; align-items:center; gap:8px; padding:12px 16px; background:var(--bg2); border-bottom:1px solid var(--border); position:sticky; top:0; z-index:100; overflow-x:auto; }
.nav-brand { font-family:'Playfair Display',serif; font-size:18px; font-weight:800; color:var(--accent2); white-space:nowrap; margin-right:8px; letter-spacing:-0.5px; }
.nav-btn { padding:8px 14px; border-radius:20px; border:1px solid transparent; background:transparent; color:var(--text2); font-size:13px; font-weight:500; cursor:pointer; white-space:nowrap; transition:all 0.2s; font-family:inherit; }
.nav-btn:hover { background:var(--bg3); color:var(--text); }
.nav-btn.active { background:var(--accent); color:#fff; border-color:var(--accent); box-shadow:0 2px 12px var(--accent-glow); }

/* ── MAIN CONTENT ── */
.main { flex:1; padding:20px 16px 100px; max-width:1200px; margin:0 auto; width:100%; }

.section-title { font-family:'Playfair Display',serif; font-size:26px; font-weight:800; margin-bottom:16px; letter-spacing:-0.5px; }
.section-sub { color:var(--text2); font-size:14px; margin-bottom:20px; }

/* ── CARDS ── */
.card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:16px; transition:all 0.2s; }
.card:hover { border-color:var(--accent); box-shadow:0 0 0 1px var(--accent-glow); }

.recipe-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:14px; }
.recipe-card { cursor:pointer; position:relative; }
.recipe-card .rc-title { font-weight:700; font-size:15px; margin-bottom:8px; padding-right:72px; line-height:1.3; }
.recipe-card .rc-macros { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px; align-items:center; }
.recipe-card .rc-macros .macro-lg { font-size:14px; font-weight:700; letter-spacing:-0.2px; padding:3px 9px; border-radius:8px; display:inline-block; line-height:1.3; }
.macro-lg.protein { background:rgba(62,207,142,0.12); color:var(--green); }
.macro-lg.carbs { background:rgba(255,179,71,0.12); color:var(--orange); }
.macro-lg.fat { background:rgba(255,107,107,0.12); color:var(--red); }
.macro-lg.fiber { background:rgba(34,211,238,0.12); color:var(--cyan); }
.macro-lg.cal-total { background:rgba(255,212,59,0.12); color:var(--yellow); }
.recipe-card .rc-meta { font-size:12px; color:var(--text2); display:flex; gap:10px; flex-wrap:wrap; margin-bottom:6px; }
.recipe-card .rc-tags { display:flex; gap:4px; flex-wrap:wrap; }
.tag { display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px; font-weight:500; background:var(--bg3); color:var(--text2); border:1px solid var(--border); }
.tag.active { background:var(--accent-glow); color:var(--accent2); border-color:var(--accent); }

.rc-book { position:absolute; top:12px; right:12px; padding:2px 8px; border-radius:6px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:1px; }
.rc-book.sc { background:rgba(255,107,107,0.15); color:var(--red); }
.rc-book.mp { background:rgba(62,207,142,0.15); color:var(--green); }

.badge { display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:8px; font-size:12px; font-weight:600; }
.badge-accent { background:var(--accent-glow); color:var(--accent2); }
.badge-green { background:rgba(62,207,142,0.12); color:var(--green); }
.badge-orange { background:rgba(255,179,71,0.12); color:var(--orange); }

/* ── INPUTS ── */
.input { padding:10px 14px; border-radius:var(--radius); border:1px solid var(--border); background:var(--bg3); color:var(--text); font-size:14px; font-family:inherit; width:100%; transition:border-color 0.2s; outline:none; }
.input:focus { border-color:var(--accent); }
.input::placeholder { color:var(--text2); opacity:0.6; }

select.input { cursor:pointer; appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239498ab' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 12px center; padding-right:32px; }

.btn { padding:10px 20px; border-radius:var(--radius); border:none; font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; transition:all 0.2s; display:inline-flex; align-items:center; gap:6px; }
.btn-primary { background:var(--accent); color:#fff; }
.btn-primary:hover { background:var(--accent2); box-shadow:0 4px 16px var(--accent-glow); }
.btn-sm { padding:6px 12px; font-size:12px; border-radius:8px; }
.btn-outline { background:transparent; border:1px solid var(--border); color:var(--text2); }
.btn-outline:hover { border-color:var(--accent); color:var(--text); }
.btn-danger { background:rgba(255,107,107,0.15); color:var(--red); border:1px solid rgba(255,107,107,0.3); }
.btn-danger:hover { background:rgba(255,107,107,0.25); }
.btn-green { background:rgba(62,207,142,0.15); color:var(--green); border:1px solid rgba(62,207,142,0.3); }
.btn-green:hover { background:rgba(62,207,142,0.25); }

/* ── TABLES ── */
.table-wrap { overflow-x:auto; border-radius:var(--radius); border:1px solid var(--border); }
table { width:100%; border-collapse:collapse; font-size:13px; }
th { background:var(--bg3); padding:10px 12px; text-align:left; font-weight:600; color:var(--text2); font-size:12px; text-transform:uppercase; letter-spacing:0.5px; border-bottom:1px solid var(--border); white-space:nowrap; }
td { padding:10px 12px; border-bottom:1px solid var(--border); }
tr:last-child td { border-bottom:none; }
tr:hover td { background:rgba(108,99,255,0.04); }

/* ── INGREDIENT BREAKDOWN ── */
.ing-row { cursor:pointer; }
.ing-row:hover td { background:rgba(108,99,255,0.08); }
.ing-toggle { font-size:10px; margin-right:6px; display:inline-block; transition:transform 0.2s; }
.ing-toggle.open { transform:rotate(90deg); }
.ing-detail { background:var(--bg2); }
.ing-detail td { padding:6px 12px; font-size:12px; border-bottom:1px solid rgba(46,51,71,0.5); }
.ing-detail:last-child td { border-bottom:none; }
.alt-units { color:var(--text2); font-size:11px; margin-top:2px; }
.store-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); gap:8px; margin-top:8px; }
.store-price-card { background:var(--bg3); border:1px solid var(--border); border-radius:8px; padding:8px 10px; font-size:12px; }
.store-price-card .store-name { font-weight:600; font-size:11px; margin-bottom:2px; }
.store-price-card .store-cost { font-weight:700; font-size:16px; }
.store-price-card.kroger .store-cost { color:var(--kroger); }
.store-price-card.sams .store-cost { color:var(--sams); }
.store-totals { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:10px; margin-top:12px; }
.store-total-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:12px; text-align:center; }
.store-total-card .st-name { font-size:11px; color:var(--text2); text-transform:uppercase; letter-spacing:0.5px; margin-bottom:4px; }
.store-total-card .st-price { font-size:22px; font-weight:700; }
.sort-chip { display:inline-flex; align-items:center; gap:4px; padding:4px 10px; border-radius:16px; font-size:11px; font-weight:600; background:var(--bg3); color:var(--text2); border:1px solid var(--border); cursor:pointer; }
.sort-chip.active { background:var(--accent-glow); color:var(--accent2); border-color:var(--accent); }
.sort-chip .sort-dir { font-size:9px; }

/* ── FILTERS ── */
.filters { display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap; align-items:center; }

/* ── MEAL PLAN ── */
.plan-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:10px; }
.plan-day { text-align:center; }
.plan-day-head { font-weight:700; font-size:13px; padding:6px; background:var(--bg3); border-radius:8px 8px 0 0; }
.plan-day-body { background:var(--surface); border:1px solid var(--border); border-top:none; border-radius:0 0 8px 8px; padding:8px; min-height:80px; }
.plan-meal { font-size:11px; padding:4px 6px; margin:3px 0; background:var(--bg3); border-radius:6px; cursor:pointer; transition:all 0.15s; border:1px solid transparent; }
.plan-meal:hover { border-color:var(--accent); }
.plan-meal .pm-remove { color:var(--red); cursor:pointer; margin-left:4px; font-size:13px; }

/* ── STAR RATING ── */
.stars { display:inline-flex; gap:2px; }
.star { cursor:pointer; font-size:20px; color:var(--border); transition:color 0.15s; }
.star.filled { color:var(--yellow); }
.star:hover { color:var(--orange); }

/* ── SOURCE VIEWER OVERLAY ── */
.sv-fab { position:fixed; bottom:24px; right:24px; z-index:200; width:52px; height:52px; border-radius:50%; background:var(--accent); color:#fff; border:none; font-size:22px; cursor:pointer; box-shadow:0 4px 20px rgba(108,99,255,0.4); display:flex; align-items:center; justify-content:center; transition:all 0.25s; }
.sv-fab:hover { transform:scale(1.1); box-shadow:0 6px 28px rgba(108,99,255,0.55); }

.sv-overlay { position:fixed; inset:0; z-index:300; background:rgba(0,0,0,0.8); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; animation:fadeIn 0.2s ease; }
@keyframes fadeIn { from{opacity:0} to{opacity:1} }

.sv-modal { background:var(--bg2); border:1px solid var(--border); border-radius:16px; width:92vw; max-width:900px; max-height:90vh; display:flex; flex-direction:column; box-shadow:0 24px 64px rgba(0,0,0,0.5); }
.sv-header { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid var(--border); }
.sv-header h3 { font-family:'Playfair Display',serif; font-size:18px; }
.sv-close { width:36px; height:36px; border-radius:50%; background:var(--bg3); border:1px solid var(--border); color:var(--text); font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.15s; }
.sv-close:hover { background:var(--red); color:#fff; border-color:var(--red); }
.sv-body { flex:1; overflow:auto; padding:20px; display:flex; flex-direction:column; align-items:center; gap:16px; }
.sv-select { display:flex; gap:10px; align-items:center; flex-wrap:wrap; width:100%; }
.sv-img-wrap { flex:1; display:flex; align-items:center; justify-content:center; overflow:auto; width:100%; }
.sv-img-wrap img { max-width:100%; border-radius:8px; box-shadow:var(--shadow); }
.sv-placeholder { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px; color:var(--text2); padding:40px; text-align:center; }
.sv-placeholder .sv-icon { font-size:48px; opacity:0.3; }

/* ── PANTRY ── */
.pantry-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
.pantry-item { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.pantry-item .pi-name { flex:1; font-size:14px; }
.pantry-item .pi-amt { font-size:13px; color:var(--text2); }

/* ── RECOMMEND ── */
.rec-bar { height:8px; border-radius:4px; background:var(--bg3); overflow:hidden; flex:1; }
.rec-bar-fill { height:100%; border-radius:4px; background:linear-gradient(90deg,var(--accent),var(--accent2)); transition:width 0.3s; }

/* ── MACRO BAR ── */
.macro-strip { display:flex; gap:6px; margin-top:8px; flex-wrap:wrap; }
.macro-chip { display:inline-flex; align-items:center; gap:3px; padding:2px 7px; border-radius:6px; font-size:10px; font-weight:600; line-height:1.4; }
.macro-chip.protein { background:rgba(62,207,142,0.12); color:var(--green); }
.macro-chip.carbs { background:rgba(255,179,71,0.12); color:var(--orange); }
.macro-chip.fat { background:rgba(255,107,107,0.12); color:var(--red); }
.macro-chip.fiber { background:rgba(34,211,238,0.12); color:var(--cyan); }
.macro-chip.cal-total { background:rgba(255,212,59,0.12); color:var(--yellow); }

.macro-detail-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr)); gap:10px; margin-top:8px; }
.macro-detail-card { padding:10px; border-radius:10px; text-align:center; }
.macro-detail-card .md-val { font-size:22px; font-weight:700; line-height:1.2; }
.macro-detail-card .md-label { font-size:11px; font-weight:500; opacity:0.8; }
.macro-detail-card .md-sub { font-size:10px; opacity:0.5; margin-top:2px; }
.macro-detail-card.protein { background:rgba(62,207,142,0.08); color:var(--green); border:1px solid rgba(62,207,142,0.2); }
.macro-detail-card.carbs { background:rgba(255,179,71,0.08); color:var(--orange); border:1px solid rgba(255,179,71,0.2); }
.macro-detail-card.fat { background:rgba(255,107,107,0.08); color:var(--red); border:1px solid rgba(255,107,107,0.2); }
.macro-detail-card.fiber { background:rgba(34,211,238,0.08); color:var(--cyan); border:1px solid rgba(34,211,238,0.2); }
.macro-detail-card.calories { background:rgba(255,212,59,0.08); color:var(--yellow); border:1px solid rgba(255,212,59,0.2); }

/* ── MISC ── */
.empty-state { text-align:center; padding:40px 20px; color:var(--text2); }
.empty-state .es-icon { font-size:40px; margin-bottom:12px; opacity:0.4; }
.divider { height:1px; background:var(--border); margin:20px 0; }
.flex { display:flex; }
.flex-wrap { flex-wrap:wrap; }
.items-center { align-items:center; }
.justify-between { justify-content:space-between; }
.gap-2 { gap:8px; }
.gap-3 { gap:12px; }
.gap-4 { gap:16px; }
.mb-2 { margin-bottom:8px; }
.mb-3 { margin-bottom:12px; }
.mb-4 { margin-bottom:16px; }
.mt-2 { margin-top:8px; }
.mt-3 { margin-top:12px; }
.w-full { width:100%; }
.text-sm { font-size:13px; }
.text-xs { font-size:11px; }
.text-muted { color:var(--text2); }
.fw-600 { font-weight:600; }
.fw-700 { font-weight:700; }
.truncate { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ── SCROLLBAR ── */
::-webkit-scrollbar { width:6px; height:6px; }
::-webkit-scrollbar-track { background:var(--bg); }
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
::-webkit-scrollbar-thumb:hover { background:var(--text2); }

/* ── TOAST ── */
.toast-wrap { position:fixed; top:20px; left:50%; transform:translateX(-50%); z-index:500; pointer-events:none; }
.toast { padding:12px 24px; border-radius:12px; font-size:14px; font-weight:600; font-family:inherit; animation:toastIn 0.3s ease, toastOut 0.4s ease 2.4s forwards; pointer-events:auto; box-shadow:0 8px 32px rgba(0,0,0,0.4); max-width:90vw; text-align:center; }
.toast.success { background:var(--green); color:#0f1117; }
.toast.error { background:var(--red); color:#fff; }
@keyframes toastIn { from{opacity:0;transform:translateY(-16px)} to{opacity:1;transform:translateY(0)} }
@keyframes toastOut { from{opacity:1} to{opacity:0;transform:translateY(-10px)} }

/* ── SCALE MODAL ── */
.scale-overlay { position:fixed; inset:0; z-index:250; background:rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; }
.scale-modal { background:var(--bg2); border:1px solid var(--border); border-radius:16px; padding:24px; width:90vw; max-width:420px; }

/* ── DETAIL PANEL ── */
.detail-panel { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:20px; margin-bottom:16px; }
.detail-panel .dp-title { font-family:'Playfair Display',serif; font-size:22px; font-weight:800; margin-bottom:8px; }

/* Responsive */
@media(max-width:640px) {
  .nav { padding:8px 10px; }
  .nav-brand { font-size:14px; }
  .nav-btn { padding:6px 10px; font-size:12px; }
  .main { padding:12px 10px 100px; }
  .section-title { font-size:22px; }
  .recipe-grid { grid-template-columns:1fr; }
  .plan-grid { grid-template-columns:repeat(auto-fill,minmax(130px,1fr)); }
}
`;

// ─────────────────────────────────────────────────────────────────────────
// DATE HELPERS
// ─────────────────────────────────────────────────────────────────────────
// Shared utilities for consistent date formatting across the app.
// getDateStr → "YYYY-MM-DD" (used as mealPlan object keys)
// addDays    → date arithmetic
// dayName    → "Mon", "Tue", etc
// dayLabel   → "Apr 14", etc
// ─────────────────────────────────────────────────────────────────────────
function getDateStr(d) {
  return d.toISOString().split("T")[0];
}
function addDays(d, n) {
  const r = new Date(d);
  r.setDate(r.getDate() + n);
  return r;
}
function dayName(d) {
  return d.toLocaleDateString("en-US", { weekday: "short" });
}
function dayLabel(d) {
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

// ═════════════════════════════════════════════════════════════════════════
// MAIN APP COMPONENT
// ═════════════════════════════════════════════════════════════════════════
// Root component. Owns all global state:
//   - page              current active view (recipes|plan|shopping|pantry|ratings)
//   - mealPlan          { "YYYY-MM-DD": [{ recipeId, meal: 'A'|'B' }, ...] }
//   - purchaseDay       anchor date for meal plan (YYYY-MM-DD)
//   - planWeeks         1|2|3 weeks visible in plan grid
//   - pantry            array of { name, amt, unit } user-tracked staples
//   - ratings           { recipeId: 1..5 }
//   - svOpen / svRecipeId  Source Viewer overlay state (persists across pages)
//   - scaleModal        recipe scaling modal { recipeId, targetServings }
//   - toast             transient notification { message, type }
//   - detailId          currently-viewed recipe detail (null = list view)
//   - search / filter*  recipe browser filter state
//
// Computed (useMemo) values:
//   - planDays          array of Date objects for the plan grid
//   - totalMealsNeeded  planDays.length * 2 (2 meals/day)
//   - plannedRecipes    { recipeId: count } across the plan
//   - shoppingList      derived from plannedRecipes (one entry per recipe)
//   - nextPurchaseDate  purchaseDay + floor(mealsPlanned / 2) days
//   - recommendations   tag-weighted match scores from ratings
//   - filteredRecipes   applies search + all filter dropdowns
// ═════════════════════════════════════════════════════════════════════════

// localStorage helper — returns parsed value or fallback on any error
function loadState(key, fallback) {
  try {
    const v = localStorage.getItem(key);
    return v !== null ? JSON.parse(v) : fallback;
  } catch {
    return fallback;
  }
}

function App() {
  const [page, setPage] = useState("recipes");
  const [recipes] = useState(RECIPE_DB);

  // Meal Plan state (persisted to localStorage)
  const [mealPlan, setMealPlan] = useState(() => loadState("mmp_mealPlan", {}));
  const [purchaseDay, setPurchaseDay] = useState(() =>
    loadState("mmp_purchaseDay", getDateStr(new Date())),
  );
  const [planWeeks, setPlanWeeks] = useState(() =>
    loadState("mmp_planWeeks", 1),
  );

  // Pantry state (persisted to localStorage)
  const defaultPantry = [
    { name: "olive oil", amt: 1, unit: "bottle" },
    { name: "salt", amt: 1, unit: "box" },
    { name: "black pepper", amt: 1, unit: "jar" },
    { name: "garlic powder", amt: 1, unit: "jar" },
    { name: "cumin", amt: 1, unit: "jar" },
    { name: "soy sauce", amt: 1, unit: "bottle" },
  ];
  const [pantry, setPantry] = useState(() =>
    loadState("mmp_pantry", defaultPantry),
  );

  // Ratings state (persisted to localStorage)
  const [ratings, setRatings] = useState(() => loadState("mmp_ratings", {}));

  // Persist state changes to localStorage
  useEffect(() => {
    localStorage.setItem("mmp_mealPlan", JSON.stringify(mealPlan));
  }, [mealPlan]);
  useEffect(() => {
    localStorage.setItem("mmp_purchaseDay", JSON.stringify(purchaseDay));
  }, [purchaseDay]);
  useEffect(() => {
    localStorage.setItem("mmp_planWeeks", JSON.stringify(planWeeks));
  }, [planWeeks]);
  useEffect(() => {
    localStorage.setItem("mmp_pantry", JSON.stringify(pantry));
  }, [pantry]);
  useEffect(() => {
    localStorage.setItem("mmp_ratings", JSON.stringify(ratings));
  }, [ratings]);

  // Source Viewer
  const [svOpen, setSvOpen] = useState(false);
  const [svRecipeId, setSvRecipeId] = useState(null);

  // Scale modal
  const [scaleModal, setScaleModal] = useState(null); // { recipeId, targetServings }

  // Toast notification
  const [toast, setToast] = useState(null); // { message, type:'success'|'error' }
  const toastTimer = useRef(null);
  const showToast = useCallback((message, type = "success") => {
    if (toastTimer.current) clearTimeout(toastTimer.current);
    setToast({ message, type });
    toastTimer.current = setTimeout(() => setToast(null), 2800);
  }, []);

  // Recipe detail
  const [detailId, setDetailId] = useState(null);

  // Search/filter
  const [search, setSearch] = useState("");
  const [filterCat, setFilterCat] = useState("All");
  const [filterTag, setFilterTag] = useState("All");
  const [filterBook, setFilterBook] = useState("All");
  const [filterSection, setFilterSection] = useState("All");

  // Open source viewer for a specific recipe
  const openSourceViewer = useCallback((recipeId = null) => {
    setSvRecipeId(recipeId);
    setSvOpen(true);
  }, []);

  // ── COMPUTED: plan days ──
  const planDays = useMemo(() => {
    const start = new Date(purchaseDay + "T12:00:00");
    const days = [];
    for (let i = 0; i < planWeeks * 7; i++) {
      days.push(addDays(start, i));
    }
    return days;
  }, [purchaseDay, planWeeks]);

  // ── COMPUTED: total meals needed ──
  const totalMealsNeeded = planDays.length * 2;

  // ── COMPUTED: planned recipes with servings ──
  const plannedRecipes = useMemo(() => {
    const map = {};
    Object.values(mealPlan)
      .flat()
      .forEach((m) => {
        if (!map[m.recipeId]) map[m.recipeId] = 0;
        map[m.recipeId]++;
      });
    return map;
  }, [mealPlan]);

  // ── COMPUTED: shopping list ──
  // Shopping list: since individual ingredients aren't parsed from PDFs,
  // we show planned recipes as line items with estimated costs based on servings needed
  const shoppingList = useMemo(() => {
    const items = [];
    Object.entries(plannedRecipes).forEach(([rid, count]) => {
      const r = recipes.find((x) => x.id === +rid);
      if (!r) return;
      const batches = Math.ceil(count / r.servings);
      items.push({
        name: r.title,
        recipeId: r.id,
        servingsNeeded: count,
        batchesNeeded: batches,
        servingsPerBatch: r.servings,
        totalServings: batches * r.servings,
        calories: r.calories,
        protein: r.protein,
        category: r.category,
        book: r.book,
        page: r.page,
        section: r.section,
      });
    });
    return items;
  }, [plannedRecipes, recipes]);

  // ── COMPUTED: next purchase day ──
  const mealsPlanned = Object.values(mealPlan).flat().length;
  const daysOfFood = Math.floor(mealsPlanned / 2);
  const nextPurchaseDate = useMemo(() => {
    const start = new Date(purchaseDay + "T12:00:00");
    return addDays(start, daysOfFood);
  }, [purchaseDay, daysOfFood]);

  // ── COMPUTED: recommendations ──
  const recommendations = useMemo(() => {
    const ratedRecipes = Object.entries(ratings).filter(([, v]) => v > 0);
    if (ratedRecipes.length === 0)
      return recipes.map((r) => ({ ...r, score: 50 }));

    // build tag weights from ratings
    const tagWeights = {};
    ratedRecipes.forEach(([rid, rating]) => {
      const r = recipes.find((x) => x.id === +rid);
      if (!r) return;
      r.tags.forEach((t) => {
        if (!tagWeights[t]) tagWeights[t] = { sum: 0, count: 0 };
        tagWeights[t].sum += rating;
        tagWeights[t].count++;
      });
    });

    return recipes
      .map((r) => {
        let score = 0,
          count = 0;
        r.tags.forEach((t) => {
          if (tagWeights[t]) {
            score += tagWeights[t].sum / tagWeights[t].count;
            count++;
          }
        });
        const avgScore = count > 0 ? score / count : 2.5;
        // bonus for unrated
        const isRated = ratings[r.id] !== undefined;
        return { ...r, score: Math.round((avgScore / 5) * 100), isRated };
      })
      .sort((a, b) => b.score - a.score);
  }, [ratings, recipes]);

  // ─── STATE MUTATION HANDLERS ────────────────────────────────────────
  // Each handler triggers a toast notification for user feedback.
  // addToPlan: rejects with error toast if slot already taken.
  // removeFromPlan: silently removes; the grid UI already reflects change.
  // Pantry add: confirms with success toast.
  // setRating: silent (UI already updates via star click feedback).
  // ───────────────────────────────────────────────────────────────────
  const addToPlan = (recipeId, dateStr, meal) => {
    const day = mealPlan[dateStr] || [];
    if (day.find((m) => m.meal === meal)) {
      showToast(
        `Meal ${meal === "A" ? "1" : "2"} slot is already taken for that day`,
        "error",
      );
      return;
    }
    const recipeName =
      recipes.find((r) => r.id === recipeId)?.title || "Recipe";
    const dateObj = new Date(dateStr + "T12:00:00");
    const dayStr = dateObj.toLocaleDateString("en-US", {
      weekday: "short",
      month: "short",
      day: "numeric",
    });
    setMealPlan((prev) => {
      const d = prev[dateStr] || [];
      return { ...prev, [dateStr]: [...d, { recipeId, meal }] };
    });
    showToast(
      `✓ Added "${recipeName}" to ${dayStr} · Meal ${meal === "A" ? "1" : "2"}`,
      "success",
    );
  };

  const removeFromPlan = (dateStr, meal) => {
    setMealPlan((prev) => {
      const day = (prev[dateStr] || []).filter((m) => m.meal !== meal);
      const next = { ...prev };
      if (day.length === 0) delete next[dateStr];
      else next[dateStr] = day;
      return next;
    });
  };

  const addPantryItem = (name, amt, unit) => {
    setPantry((prev) => [...prev, { name, amt, unit }]);
    showToast(`✓ Added "${name}" to pantry`, "success");
  };

  const removePantryItem = (idx) => {
    setPantry((prev) => prev.filter((_, i) => i !== idx));
  };

  const setRating = (recipeId, val) => {
    setRatings((prev) => ({ ...prev, [recipeId]: val }));
  };

  // ── FILTERED RECIPES ──
  const filteredRecipes = useMemo(() => {
    return recipes.filter((r) => {
      if (search && !r.title.toLowerCase().includes(search.toLowerCase()))
        return false;
      if (filterCat !== "All" && r.category !== filterCat) return false;
      if (filterTag !== "All" && !r.tags.includes(filterTag)) return false;
      if (filterBook !== "All" && r.book !== filterBook) return false;
      if (filterSection !== "All" && r.section !== filterSection) return false;
      return true;
    });
  }, [recipes, search, filterCat, filterTag, filterBook, filterSection]);

  // ── PAGES ──
  const PAGES = [
    { id: "recipes", label: "📖 Recipes", icon: "📖" },
    { id: "plan", label: "📅 Meal Plan", icon: "📅" },
    { id: "shopping", label: "🛒 Shopping", icon: "🛒" },
    { id: "pantry", label: "🏠 Pantry", icon: "🏠" },
    { id: "ratings", label: "⭐ Ratings", icon: "⭐" },
  ];

  return (
    <>
      <style>{CSS}</style>
      <div className="app">
        {/* NAV */}
        <nav className="nav">
          <div className="nav-brand">Mighty Meal</div>
          {PAGES.map((p) => (
            <button
              key={p.id}
              className={`nav-btn ${page === p.id ? "active" : ""}`}
              onClick={() => {
                setPage(p.id);
                setDetailId(null);
              }}
            >
              {p.label}
            </button>
          ))}
        </nav>

        {/* MAIN */}
        <div className="main">
          {page === "recipes" && (
            <RecipesPage
              recipes={filteredRecipes}
              search={search}
              setSearch={setSearch}
              filterCat={filterCat}
              setFilterCat={setFilterCat}
              filterTag={filterTag}
              setFilterTag={setFilterTag}
              filterBook={filterBook}
              setFilterBook={setFilterBook}
              filterSection={filterSection}
              setFilterSection={setFilterSection}
              detailId={detailId}
              setDetailId={setDetailId}
              ratings={ratings}
              setRating={setRating}
              openSourceViewer={openSourceViewer}
              addToPlan={addToPlan}
              planDays={planDays}
              mealPlan={mealPlan}
              setScaleModal={setScaleModal}
            />
          )}
          {page === "plan" && (
            <MealPlanPage
              recipes={recipes}
              mealPlan={mealPlan}
              planDays={planDays}
              purchaseDay={purchaseDay}
              setPurchaseDay={setPurchaseDay}
              planWeeks={planWeeks}
              setPlanWeeks={setPlanWeeks}
              removeFromPlan={removeFromPlan}
              totalMealsNeeded={totalMealsNeeded}
              mealsPlanned={mealsPlanned}
              nextPurchaseDate={nextPurchaseDate}
              openSourceViewer={openSourceViewer}
              addToPlan={addToPlan}
            />
          )}
          {page === "shopping" && (
            <ShoppingPage
              shoppingList={shoppingList}
              purchaseDay={purchaseDay}
              nextPurchaseDate={nextPurchaseDate}
              openSourceViewer={openSourceViewer}
              recipes={recipes}
              plannedRecipes={plannedRecipes}
            />
          )}
          {page === "pantry" && (
            <PantryPage
              pantry={pantry}
              addPantryItem={addPantryItem}
              removePantryItem={removePantryItem}
            />
          )}
          {page === "ratings" && (
            <RatingsPage
              recipes={recipes}
              ratings={ratings}
              setRating={setRating}
              recommendations={recommendations}
              openSourceViewer={openSourceViewer}
            />
          )}
        </div>

        {/* SOURCE VIEWER FAB */}
        <button
          className="sv-fab"
          onClick={() => openSourceViewer(svRecipeId)}
          title="Open Source Viewer"
        >
          📄
        </button>

        {/* SOURCE VIEWER OVERLAY */}
        {svOpen && (
          <SourceViewerOverlay
            recipes={recipes}
            recipeId={svRecipeId}
            setRecipeId={setSvRecipeId}
            onClose={() => setSvOpen(false)}
          />
        )}

        {/* SCALE MODAL */}
        {scaleModal && (
          <ScaleModal
            recipe={recipes.find((r) => r.id === scaleModal.recipeId)}
            onClose={() => setScaleModal(null)}
          />
        )}

        {/* TOAST NOTIFICATION */}
        {toast && (
          <div className="toast-wrap">
            <div className={`toast ${toast.type}`}>{toast.message}</div>
          </div>
        )}
      </div>
    </>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// RECIPES PAGE — Browse, search, filter, sort all recipes
// ═════════════════════════════════════════════════════════════════════════
// If detailId is set, renders RecipeDetail (full recipe view) instead of grid.
// Card hierarchy: Title (tier 1) → Macros+Cal (tier 2) → Meta+Tags (tier 3).
// Sort options: A→Z, Calories ↑/↓, Protein ↓, Servings ↓, Fastest, Top Rated.
// Filters: Category, Book (SC/MP), Section (meals/proteins/burritos/etc), Tag.
// ═════════════════════════════════════════════════════════════════════════
function RecipesPage({
  recipes,
  search,
  setSearch,
  filterCat,
  setFilterCat,
  filterTag,
  setFilterTag,
  filterBook,
  setFilterBook,
  filterSection,
  setFilterSection,
  detailId,
  setDetailId,
  ratings,
  setRating,
  openSourceViewer,
  addToPlan,
  planDays,
  mealPlan,
  setScaleModal,
}) {
  const detail = detailId ? RECIPE_DB.find((r) => r.id === detailId) : null;

  // Multi-tier sort: array of { field, dir } objects applied in order
  const SORT_FIELDS = [
    { key: "name", label: "Name" },
    { key: "calories", label: "Calories" },
    { key: "protein", label: "Protein" },
    { key: "carbs", label: "Carbs" },
    { key: "fat", label: "Fat" },
    { key: "fiber", label: "Fiber" },
    { key: "servings", label: "Servings" },
    { key: "rating", label: "Rating" },
  ];
  const [sortTiers, setSortTiers] = useState([{ field: "name", dir: "asc" }]);

  const toggleSort = (field) => {
    setSortTiers((prev) => {
      const idx = prev.findIndex((t) => t.field === field);
      if (idx === -1) return [...prev, { field, dir: "desc" }];
      if (prev[idx].dir === "desc")
        return prev.map((t, i) => (i === idx ? { ...t, dir: "asc" } : t));
      return prev.filter((_, i) => i !== idx); // third click removes
    });
  };

  const sortedRecipes = useMemo(() => {
    const arr = [...recipes];
    return arr.sort((a, b) => {
      for (const tier of sortTiers) {
        let cmp = 0;
        if (tier.field === "name") cmp = a.title.localeCompare(b.title);
        else if (tier.field === "rating")
          cmp = (ratings[a.id] || 0) - (ratings[b.id] || 0);
        else cmp = (a[tier.field] || 0) - (b[tier.field] || 0);
        if (tier.dir === "desc") cmp = -cmp;
        if (cmp !== 0) return cmp;
      }
      return 0;
    });
  }, [recipes, sortTiers, ratings]);

  if (detail) {
    return (
      <RecipeDetail
        recipe={detail}
        onBack={() => setDetailId(null)}
        rating={ratings[detail.id] || 0}
        setRating={(v) => setRating(detail.id, v)}
        openSourceViewer={openSourceViewer}
        addToPlan={addToPlan}
        planDays={planDays}
        mealPlan={mealPlan}
        setScaleModal={setScaleModal}
      />
    );
  }

  return (
    <div>
      <h1 className="section-title">Recipe Collection</h1>
      <p className="section-sub">
        {recipes.length} recipes from Tom Walsh's Stealth Health Slow Cooker &
        Meal Prep cookbooks
      </p>

      <div className="filters">
        <input
          className="input"
          style={{ maxWidth: 240 }}
          placeholder="Search recipes…"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        />
        <select
          className="input"
          style={{ maxWidth: 150 }}
          value={filterCat}
          onChange={(e) => setFilterCat(e.target.value)}
        >
          <option value="All">All Categories</option>
          {CATEGORIES.map((c) => (
            <option key={c} value={c}>
              {c}
            </option>
          ))}
        </select>
        <select
          className="input"
          style={{ maxWidth: 150 }}
          value={filterBook}
          onChange={(e) => setFilterBook(e.target.value)}
        >
          <option value="All">All Books</option>
          <option value="sc">Slow Cooker</option>
          <option value="mp">Meal Prep 2024</option>
        </select>
        <select
          className="input"
          style={{ maxWidth: 160 }}
          value={filterSection}
          onChange={(e) => setFilterSection(e.target.value)}
        >
          <option value="All">All Sections</option>
          <option value="meals">SC: Full Meals</option>
          <option value="proteins">Proteins</option>
          <option value="500cal">MP: 500 Cal Meals</option>
          <option value="burritos">MP: Burritos+</option>
          <option value="counter">MP: Counter</option>
        </select>
        <select
          className="input"
          style={{ maxWidth: 150 }}
          value={filterTag}
          onChange={(e) => setFilterTag(e.target.value)}
        >
          <option value="All">All Tags</option>
          {ALL_TAGS.map((t) => (
            <option key={t} value={t}>
              {t}
            </option>
          ))}
        </select>
      </div>

      {/* Multi-tier sort chips */}
      <div
        className="flex gap-2 mb-3"
        style={{ flexWrap: "wrap", alignItems: "center" }}
      >
        <span className="text-xs text-muted" style={{ marginRight: 4 }}>
          Sort:
        </span>
        {SORT_FIELDS.map((sf) => {
          const tier = sortTiers.find((t) => t.field === sf.key);
          const idx = sortTiers.findIndex((t) => t.field === sf.key);
          return (
            <span
              key={sf.key}
              className={`sort-chip ${tier ? "active" : ""}`}
              onClick={() => toggleSort(sf.key)}
            >
              {tier && (
                <span style={{ fontSize: 9, opacity: 0.7 }}>{idx + 1}</span>
              )}
              {sf.label}
              {tier && (
                <span className="sort-dir">
                  {tier.dir === "desc" ? "▼" : "▲"}
                </span>
              )}
            </span>
          );
        })}
        {sortTiers.length > 1 && (
          <span
            className="sort-chip"
            style={{
              color: "var(--red)",
              borderColor: "rgba(255,107,107,0.3)",
            }}
            onClick={() => setSortTiers([{ field: "name", dir: "asc" }])}
          >
            ✕ Reset
          </span>
        )}
      </div>

      {sortedRecipes.length === 0 ? (
        <div className="empty-state">
          <div className="es-icon">🔍</div>No recipes match your filters.
        </div>
      ) : (
        <div className="recipe-grid">
          {sortedRecipes.map((r) => (
            <div
              key={r.id}
              className="card recipe-card"
              onClick={() => setDetailId(r.id)}
            >
              <span className={`rc-book ${r.book}`}>
                {r.book === "sc" ? "Slow Cooker" : "Meal Prep"}
              </span>
              {/* TIER 1 — Title */}
              <div className="rc-title">{r.title}</div>
              {/* TIER 2 — Macros + Calories (prominent) */}
              <div className="rc-macros">
                <span className="macro-lg cal-total">🔥 {r.calories} cal</span>
                <span className="macro-lg protein">P {r.protein}g</span>
                <span className="macro-lg carbs">C {r.carbs}g</span>
                <span className="macro-lg fat">F {r.fat}g</span>
                <span className="macro-lg fiber">Fb {r.fiber}g</span>
              </div>
              {/* TIER 3 — Servings, time, tags */}
              <div className="rc-meta">
                <span>🍽 {r.servings} servings</span>
                <span>📑 {r.section}</span>
                <span>
                  🔥 {(r.calories * r.servings).toLocaleString()} total
                </span>
                {r.servings < 10 && (
                  <span style={{ color: "var(--orange)" }}>⚠ scale</span>
                )}
              </div>
              <div className="rc-tags">
                {r.tags.slice(0, 4).map((t) => (
                  <span key={t} className="tag">
                    {t}
                  </span>
                ))}
              </div>
              {ratings[r.id] > 0 && (
                <div
                  className="mt-2 text-sm"
                  style={{ color: "var(--yellow)" }}
                >
                  {"★".repeat(ratings[r.id])}
                  {"☆".repeat(5 - ratings[r.id])}
                </div>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// RECIPE DETAIL — Full single-recipe view
// ═════════════════════════════════════════════════════════════════════════
// Shown when user clicks a card in RecipesPage. Displays:
//   - Title, book, section, servings, calories badges
//   - Scale warning if recipe makes < 10 servings (min required)
//   - MacroDetail panel (5 cards: cal, P, C, F, Fb with % of calories)
//   - Star rating widget
//   - Tag chips
//   - "View Full Recipe & Ingredients" card → opens Source Viewer
//   - Add-to-plan controls (day selector + Meal 1/2 + Add button)
//   - "Fill All Empty Slots" bulk action
// ═════════════════════════════════════════════════════════════════════════
function RecipeDetail({
  recipe,
  onBack,
  rating,
  setRating,
  openSourceViewer,
  addToPlan,
  planDays,
  mealPlan,
  setScaleModal,
}) {
  const [addDate, setAddDate] = useState("");
  const [addMeal, setAddMeal] = useState("A");
  const r = recipe;
  const needsScale = r.servings < 10;

  return (
    <div>
      <button className="btn btn-outline mb-3" onClick={onBack}>
        ← Back to Recipes
      </button>
      <div className="detail-panel">
        <div
          className="flex items-center justify-between gap-3 mb-2"
          style={{ flexWrap: "wrap" }}
        >
          <h2 className="dp-title">{r.title}</h2>
          <button
            className="btn btn-sm btn-outline"
            onClick={() => openSourceViewer(r.id)}
          >
            📄 View Source
          </button>
        </div>
        <div className="flex gap-3 mb-3" style={{ flexWrap: "wrap" }}>
          <span className="badge badge-accent">🍽 {r.servings} servings</span>
          <span className="badge badge-green">
            📑{" "}
            {r.section === "meals"
              ? "Full Meal"
              : r.section === "500cal"
                ? "500 Cal Meal"
                : r.section === "burritos"
                  ? "Burrito/Handheld"
                  : r.section === "proteins"
                    ? "Protein Only"
                    : r.section === "counter"
                      ? "Counter Recipe"
                      : r.section}
          </span>
          <span className="badge badge-orange">
            🔥 {r.calories} cal/serving
          </span>
          <span
            className={`badge ${r.book === "sc" ? "badge-accent" : "badge-green"}`}
          >
            📕 {r.book === "sc" ? "Slow Cooker" : "Meal Prep"} p.{r.page}
          </span>
        </div>

        {needsScale && (
          <div
            className="card mb-3"
            style={{
              borderColor: "var(--orange)",
              background: "rgba(255,179,71,0.06)",
            }}
          >
            <div className="flex items-center justify-between gap-2">
              <span style={{ color: "var(--orange)", fontWeight: 600 }}>
                ⚠ Makes only {r.servings} servings — minimum 10 required
              </span>
              <button
                className="btn btn-sm btn-primary"
                onClick={() =>
                  setScaleModal({ recipeId: r.id, targetServings: 10 })
                }
              >
                Scale to 10+
              </button>
            </div>
          </div>
        )}

        <div className="mb-3">
          <div className="fw-700 mb-2">Nutrition per Serving</div>
          <MacroDetail r={r} />
        </div>

        <div className="mb-3">
          <div className="fw-700 mb-2">Rate this recipe</div>
          <Stars value={rating} onChange={setRating} />
        </div>

        <div className="flex gap-3 mb-3" style={{ flexWrap: "wrap" }}>
          {r.tags.map((t) => (
            <span key={t} className="tag">
              {t}
            </span>
          ))}
        </div>

        <div className="divider" />

        <h3 className="fw-700 mb-2">Ingredients</h3>
        <div
          className="card mb-3"
          style={{
            background: "var(--bg3)",
            borderColor: "var(--accent)",
            cursor: "pointer",
          }}
          onClick={() => openSourceViewer(r.id)}
        >
          <div className="flex items-center gap-3" style={{ padding: "8px 0" }}>
            <span style={{ fontSize: 28 }}>📄</span>
            <div>
              <div className="fw-700">View Full Recipe & Ingredients</div>
              <div className="text-xs text-muted">
                Open the source page from{" "}
                {r.book === "sc" ? "Slow Cooker" : "Meal Prep"} Cookbook — page{" "}
                {r.page}
              </div>
            </div>
          </div>
        </div>

        <div className="divider" />

        <h3 className="fw-700 mb-2">Add to Meal Plan</h3>
        <div className="flex gap-2 items-center" style={{ flexWrap: "wrap" }}>
          <select
            className="input"
            style={{ maxWidth: 180 }}
            value={addDate}
            onChange={(e) => setAddDate(e.target.value)}
          >
            <option value="">Select day…</option>
            {planDays.map((d) => {
              const ds = getDateStr(d);
              const dayMeals = mealPlan[ds] || [];
              return (
                <option key={ds} value={ds}>
                  {dayName(d)} {dayLabel(d)} ({dayMeals.length}/2)
                </option>
              );
            })}
          </select>
          <select
            className="input"
            style={{ maxWidth: 120 }}
            value={addMeal}
            onChange={(e) => setAddMeal(e.target.value)}
          >
            <option value="A">Meal 1</option>
            <option value="B">Meal 2</option>
          </select>
          <button
            className="btn btn-primary btn-sm"
            disabled={!addDate}
            onClick={() => {
              addToPlan(r.id, addDate, addMeal);
              setAddDate("");
            }}
          >
            Add to Plan
          </button>
        </div>
        <div className="mt-3">
          <button
            className="btn btn-sm btn-green"
            onClick={() => {
              let filled = 0;
              planDays.forEach((d) => {
                const ds = getDateStr(d);
                const dayMeals = mealPlan[ds] || [];
                if (!dayMeals.find((m) => m.meal === "A")) {
                  addToPlan(r.id, ds, "A");
                  filled++;
                }
                if (!dayMeals.find((m) => m.meal === "B")) {
                  addToPlan(r.id, ds, "B");
                  filled++;
                }
              });
            }}
          >
            Fill All Empty Slots with This Recipe
          </button>
          <span className="text-xs text-muted" style={{ marginLeft: 8 }}>
            {(() => {
              let empty = 0;
              planDays.forEach((d) => {
                const ds = getDateStr(d);
                const dayMeals = mealPlan[ds] || [];
                if (!dayMeals.find((m) => m.meal === "A")) empty++;
                if (!dayMeals.find((m) => m.meal === "B")) empty++;
              });
              return `${empty} empty slot${empty !== 1 ? "s" : ""} remaining`;
            })()}
          </span>
        </div>
      </div>
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// MEAL PLAN PAGE — Calendar grid + nutrition summary + quick-add
// ═════════════════════════════════════════════════════════════════════════
// Four stat cards at top: Purchase Day picker, Plan Duration (1-3 weeks),
// Meals Planned progress, Next Purchase Day (auto-calculated).
//
// Weekly Nutrition Summary (visible when meals > 0): totals for calories,
// protein, carbs, fat + daily averages.
//
// Quick Add bar: recipe + day + meal slot → one-click plan insertion.
//
// Plan Grid: one cell per day, each containing two meal slots (Meal 1 / 2).
// Each filled slot is clickable (opens Source Viewer) and has an ✕ to remove.
// Daily totals (cal, P, C, F) render at bottom of cell when any meal is set.
// ═════════════════════════════════════════════════════════════════════════
function MealPlanPage({
  recipes,
  mealPlan,
  planDays,
  purchaseDay,
  setPurchaseDay,
  planWeeks,
  setPlanWeeks,
  removeFromPlan,
  totalMealsNeeded,
  mealsPlanned,
  nextPurchaseDate,
  openSourceViewer,
  addToPlan,
}) {
  const getRecipe = (id) => recipes.find((r) => r.id === id);
  const [quickRecipe, setQuickRecipe] = useState("");
  const [quickDate, setQuickDate] = useState("");
  const [quickMeal, setQuickMeal] = useState("A");

  // Weekly nutrition totals
  const weeklyNutrition = useMemo(() => {
    let cal = 0,
      p = 0,
      c = 0,
      f = 0;
    Object.values(mealPlan)
      .flat()
      .forEach((m) => {
        const r = getRecipe(m.recipeId);
        if (r) {
          cal += r.calories;
          p += r.protein;
          c += r.carbs;
          f += r.fat;
        }
      });
    const days = Math.max(
      1,
      Object.keys(mealPlan).filter((k) => (mealPlan[k] || []).length > 0)
        .length,
    );
    return {
      cal,
      p,
      c,
      f,
      avgCal: Math.round(cal / days),
      avgP: Math.round(p / days),
    };
  }, [mealPlan, recipes]);

  return (
    <div>
      <h1 className="section-title">Meal Plan</h1>
      <p className="section-sub">
        2 meals per day · Plan your week · Track your next purchase day
      </p>

      <div className="flex gap-3 mb-4" style={{ flexWrap: "wrap" }}>
        <div className="card" style={{ flex: 1, minWidth: 200 }}>
          <div className="text-xs text-muted mb-2">PURCHASE DAY</div>
          <input
            type="date"
            className="input"
            value={purchaseDay}
            onChange={(e) => setPurchaseDay(e.target.value)}
          />
        </div>
        <div className="card" style={{ flex: 1, minWidth: 200 }}>
          <div className="text-xs text-muted mb-2">PLAN DURATION</div>
          <select
            className="input"
            value={planWeeks}
            onChange={(e) => setPlanWeeks(+e.target.value)}
          >
            <option value={1}>1 Week</option>
            <option value={2}>2 Weeks</option>
            <option value={3}>3 Weeks</option>
          </select>
        </div>
        <div className="card" style={{ flex: 1, minWidth: 200 }}>
          <div className="text-xs text-muted mb-2">MEALS PLANNED</div>
          <div
            className="fw-700"
            style={{
              fontSize: 22,
              color:
                mealsPlanned >= totalMealsNeeded
                  ? "var(--green)"
                  : "var(--orange)",
            }}
          >
            {mealsPlanned} / {totalMealsNeeded}
          </div>
          <div
            style={{
              height: 6,
              background: "var(--bg3)",
              borderRadius: 3,
              marginTop: 6,
            }}
          >
            <div
              style={{
                height: 6,
                borderRadius: 3,
                background:
                  mealsPlanned >= totalMealsNeeded
                    ? "var(--green)"
                    : "var(--accent)",
                width: `${Math.min(100, (mealsPlanned / totalMealsNeeded) * 100)}%`,
                transition: "width 0.3s",
              }}
            />
          </div>
        </div>
        <div className="card" style={{ flex: 1, minWidth: 200 }}>
          <div className="text-xs text-muted mb-2">NEXT PURCHASE DAY</div>
          <div
            className="fw-700"
            style={{ fontSize: 16, color: "var(--cyan)" }}
          >
            {nextPurchaseDate.toLocaleDateString("en-US", {
              weekday: "long",
              month: "short",
              day: "numeric",
            })}
          </div>
          <div className="text-xs text-muted mt-2">
            {daysOfFood(mealsPlanned)} days of food planned
          </div>
        </div>
      </div>

      {/* WEEKLY NUTRITION SUMMARY */}
      {mealsPlanned > 0 && (
        <div className="card mb-4">
          <div
            className="flex items-center justify-between mb-2"
            style={{ flexWrap: "wrap", gap: 8 }}
          >
            <div className="fw-700 text-sm">Plan Nutrition Summary</div>
            <div className="text-xs text-muted">
              Avg/day: {weeklyNutrition.avgCal} cal · {weeklyNutrition.avgP}g
              protein
            </div>
          </div>
          <div className="flex gap-3" style={{ flexWrap: "wrap" }}>
            <div
              style={{
                flex: 1,
                minWidth: 100,
                textAlign: "center",
                padding: "8px 0",
              }}
            >
              <div
                style={{
                  fontSize: 20,
                  fontWeight: 700,
                  color: "var(--yellow)",
                }}
              >
                {weeklyNutrition.cal.toLocaleString()}
              </div>
              <div className="text-xs text-muted">Total Calories</div>
            </div>
            <div
              style={{
                flex: 1,
                minWidth: 100,
                textAlign: "center",
                padding: "8px 0",
              }}
            >
              <div
                style={{ fontSize: 20, fontWeight: 700, color: "var(--green)" }}
              >
                {weeklyNutrition.p}g
              </div>
              <div className="text-xs text-muted">Total Protein</div>
            </div>
            <div
              style={{
                flex: 1,
                minWidth: 100,
                textAlign: "center",
                padding: "8px 0",
              }}
            >
              <div
                style={{
                  fontSize: 20,
                  fontWeight: 700,
                  color: "var(--orange)",
                }}
              >
                {weeklyNutrition.c}g
              </div>
              <div className="text-xs text-muted">Total Carbs</div>
            </div>
            <div
              style={{
                flex: 1,
                minWidth: 100,
                textAlign: "center",
                padding: "8px 0",
              }}
            >
              <div
                style={{ fontSize: 20, fontWeight: 700, color: "var(--red)" }}
              >
                {weeklyNutrition.f}g
              </div>
              <div className="text-xs text-muted">Total Fat</div>
            </div>
          </div>
        </div>
      )}

      {/* QUICK ADD */}
      <div className="card mb-4">
        <div className="fw-700 text-sm mb-2">Quick Add to Plan</div>
        <div className="flex gap-2 items-center" style={{ flexWrap: "wrap" }}>
          <select
            className="input"
            style={{ flex: 2, minWidth: 160 }}
            value={quickRecipe}
            onChange={(e) => setQuickRecipe(e.target.value)}
          >
            <option value="">Pick a recipe…</option>
            {recipes.map((r) => (
              <option key={r.id} value={r.id}>
                {r.title} ({r.calories} cal · P{r.protein}g)
              </option>
            ))}
          </select>
          <select
            className="input"
            style={{ flex: 1, minWidth: 140 }}
            value={quickDate}
            onChange={(e) => setQuickDate(e.target.value)}
          >
            <option value="">Day…</option>
            {planDays.map((d) => {
              const ds = getDateStr(d);
              const dm = mealPlan[ds] || [];
              return (
                <option key={ds} value={ds}>
                  {dayName(d)} {dayLabel(d)} ({dm.length}/2)
                </option>
              );
            })}
          </select>
          <select
            className="input"
            style={{ flex: 0, minWidth: 100 }}
            value={quickMeal}
            onChange={(e) => setQuickMeal(e.target.value)}
          >
            <option value="A">Meal 1</option>
            <option value="B">Meal 2</option>
          </select>
          <button
            className="btn btn-primary btn-sm"
            disabled={!quickRecipe || !quickDate}
            onClick={() => {
              addToPlan(+quickRecipe, quickDate, quickMeal);
              setQuickRecipe("");
              setQuickDate("");
            }}
          >
            + Add
          </button>
        </div>
      </div>

      <div className="plan-grid">
        {planDays.map((d) => {
          const ds = getDateStr(d);
          const dayMeals = mealPlan[ds] || [];
          const mealA = dayMeals.find((m) => m.meal === "A");
          const mealB = dayMeals.find((m) => m.meal === "B");
          const isToday = getDateStr(new Date()) === ds;

          return (
            <div key={ds} className="plan-day">
              <div
                className="plan-day-head"
                style={
                  isToday ? { background: "var(--accent)", color: "#fff" } : {}
                }
              >
                {dayName(d)} {d.getDate()}
              </div>
              <div className="plan-day-body">
                {mealA ? (
                  <div
                    className="plan-meal"
                    onClick={() => openSourceViewer(mealA.recipeId)}
                  >
                    <div className="text-xs text-muted">Meal 1</div>
                    <div className="truncate" style={{ fontSize: 11 }}>
                      {getRecipe(mealA.recipeId)?.title || "?"}
                    </div>
                    <span
                      className="pm-remove"
                      onClick={(e) => {
                        e.stopPropagation();
                        removeFromPlan(ds, "A");
                      }}
                    >
                      ✕
                    </span>
                  </div>
                ) : (
                  <div
                    className="plan-meal"
                    style={{ opacity: 0.4, cursor: "default" }}
                  >
                    <div className="text-xs">Meal 1 — empty</div>
                  </div>
                )}
                {mealB ? (
                  <div
                    className="plan-meal"
                    onClick={() => openSourceViewer(mealB.recipeId)}
                  >
                    <div className="text-xs text-muted">Meal 2</div>
                    <div className="truncate" style={{ fontSize: 11 }}>
                      {getRecipe(mealB.recipeId)?.title || "?"}
                    </div>
                    <span
                      className="pm-remove"
                      onClick={(e) => {
                        e.stopPropagation();
                        removeFromPlan(ds, "B");
                      }}
                    >
                      ✕
                    </span>
                  </div>
                ) : (
                  <div
                    className="plan-meal"
                    style={{ opacity: 0.4, cursor: "default" }}
                  >
                    <div className="text-xs">Meal 2 — empty</div>
                  </div>
                )}
                {(mealA || mealB) &&
                  (() => {
                    const rA = mealA ? getRecipe(mealA.recipeId) : null;
                    const rB = mealB ? getRecipe(mealB.recipeId) : null;
                    const dayCal = (rA?.calories || 0) + (rB?.calories || 0);
                    const dayP = (rA?.protein || 0) + (rB?.protein || 0);
                    const dayC = (rA?.carbs || 0) + (rB?.carbs || 0);
                    const dayF = (rA?.fat || 0) + (rB?.fat || 0);
                    return (
                      <div
                        style={{
                          marginTop: 4,
                          padding: "3px 0",
                          borderTop: "1px solid var(--border)",
                          fontSize: 9,
                          color: "var(--text2)",
                          display: "flex",
                          gap: 3,
                          justifyContent: "center",
                          flexWrap: "wrap",
                        }}
                      >
                        <span style={{ color: "var(--yellow)" }}>
                          {dayCal}cal
                        </span>
                        <span style={{ color: "var(--green)" }}>P{dayP}</span>
                        <span style={{ color: "var(--orange)" }}>C{dayC}</span>
                        <span style={{ color: "var(--red)" }}>F{dayF}</span>
                      </div>
                    );
                  })()}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function daysOfFood(meals) {
  return Math.floor(meals / 2);
}

// ═════════════════════════════════════════════════════════════════════════
// SHOPPING LIST PAGE — Recipe + Ingredient level with store pricing
// ═════════════════════════════════════════════════════════════════════════
// Expandable rows: click a recipe to see ingredient breakdown with:
//   - Quantity × batches, unit, and alternate unit conversions
//   - Per-ingredient cost estimate at each store location
//   - Category grouping (Meat, Produce, Dairy, etc.)
// Summary cards show totals per store for cost comparison.
// Recipes in INGREDIENT_DB show full breakdown; others show recipe-level.
// ═════════════════════════════════════════════════════════════════════════
function ShoppingPage({
  shoppingList,
  purchaseDay,
  nextPurchaseDate,
  openSourceViewer,
  recipes,
  plannedRecipes,
}) {
  const [checked, setChecked] = useState({});
  const [checkedIngredients, setCheckedIngredients] = useState({});
  const [addressedIngredients, setAddressedIngredients] = useState({});
  const [expanded, setExpanded] = useState({});
  const [selectedStore, setSelectedStore] = useState("kroger_ft");
  const toggleCheck = (id) =>
    setChecked((prev) => ({ ...prev, [id]: !prev[id] }));
  const ingredientKey = (recipeId, index) => `${recipeId}:${index}`;
  const toggleIngredientCheck = (recipeId, index) => {
    const key = ingredientKey(recipeId, index);
    const nextVal = !(checkedIngredients[key] || false);
    setCheckedIngredients((prev) => ({ ...prev, [key]: nextVal }));
    if (nextVal) {
      setAddressedIngredients((prev) => ({ ...prev, [key]: false }));
    }
  };
  const toggleIngredientAddressed = (recipeId, index) => {
    const key = ingredientKey(recipeId, index);
    const nextVal = !(addressedIngredients[key] || false);
    setAddressedIngredients((prev) => ({ ...prev, [key]: nextVal }));
    if (nextVal) {
      setCheckedIngredients((prev) => ({ ...prev, [key]: false }));
    }
  };
  const toggleRecipeCheck = (item, ingredients) => {
    const next = !checked[item.recipeId];
    setChecked((prev) => ({ ...prev, [item.recipeId]: next }));
    if (ingredients?.length) {
      setCheckedIngredients((prev) => {
        const out = { ...prev };
        ingredients.forEach((_, idx) => {
          out[ingredientKey(item.recipeId, idx)] = next;
        });
        return out;
      });
      setAddressedIngredients((prev) => {
        const out = { ...prev };
        ingredients.forEach((_, idx) => {
          out[ingredientKey(item.recipeId, idx)] = false;
        });
        return out;
      });
    }
  };
  const toggleExpand = (id) =>
    setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));

  const checkedCount = shoppingList.filter((i) => checked[i.recipeId]).length;
  const totalIngredientItems = shoppingList.reduce((sum, item) => {
    const ingredients = INGREDIENT_DB[item.recipeId];
    return sum + (ingredients ? ingredients.length : 0);
  }, 0);
  const checkedIngredientCount = shoppingList.reduce((sum, item) => {
    const ingredients = INGREDIENT_DB[item.recipeId];
    if (!ingredients) return sum;
    return (
      sum +
      ingredients.reduce((ingSum, _, idx) => {
        const key = ingredientKey(item.recipeId, idx);
        return (
          ingSum +
          (checkedIngredients[key] || addressedIngredients[key] ? 1 : 0)
        );
      }, 0)
    );
  }, 0);
  const totalServings = shoppingList.reduce((s, i) => s + i.totalServings, 0);
  const totalCal = shoppingList.reduce(
    (s, i) => s + i.calories * i.totalServings,
    0,
  );
  const totalProtein = shoppingList.reduce(
    (s, i) => s + i.protein * i.totalServings,
    0,
  );

  // Compute cost totals per store
  const storeTotals = useMemo(() => {
    const totals = {};
    STORE_LOCATIONS.forEach((s) => {
      totals[s.id] = 0;
    });
    shoppingList.forEach((item) => {
      const ingredients = INGREDIENT_DB[item.recipeId];
      if (ingredients) {
        ingredients.forEach((ing) => {
          const scaled = { ...ing, qty: ing.qty * item.batchesNeeded };
          STORE_LOCATIONS.forEach((s) => {
            totals[s.id] += ingredientCost(scaled, s.id) || 0;
          });
        });
      }
    });
    return totals;
  }, [shoppingList]);

  const hasAnyIngredients = shoppingList.some(
    (item) => INGREDIENT_DB[item.recipeId],
  );

  return (
    <div>
      <h1 className="section-title">Shopping List</h1>
      <p className="section-sub">
        Purchase Day:{" "}
        {new Date(purchaseDay + "T12:00:00").toLocaleDateString("en-US", {
          weekday: "long",
          month: "long",
          day: "numeric",
        })}
        {" · "}Next:{" "}
        {nextPurchaseDate.toLocaleDateString("en-US", {
          weekday: "long",
          month: "short",
          day: "numeric",
        })}
      </p>

      {shoppingList.length === 0 ? (
        <div className="empty-state">
          <div className="es-icon">🛒</div>Add recipes to your meal plan to
          generate a shopping list.
        </div>
      ) : (
        <>
          {/* Summary cards */}
          <div className="flex gap-3 mb-4" style={{ flexWrap: "wrap" }}>
            <div className="card" style={{ flex: 1, minWidth: 160 }}>
              <div className="text-xs text-muted mb-2">RECIPES TO COOK</div>
              <div className="fw-700" style={{ fontSize: 22 }}>
                {shoppingList.length}
              </div>
            </div>
            <div className="card" style={{ flex: 1, minWidth: 160 }}>
              <div className="text-xs text-muted mb-2">TOTAL SERVINGS</div>
              <div
                className="fw-700"
                style={{ fontSize: 22, color: "var(--accent)" }}
              >
                {totalServings}
              </div>
            </div>
            <div className="card" style={{ flex: 1, minWidth: 160 }}>
              <div className="text-xs text-muted mb-2">TOTAL NUTRITION</div>
              <div
                className="fw-700"
                style={{ fontSize: 16, color: "var(--yellow)" }}
              >
                {totalCal.toLocaleString()} cal
              </div>
              <div className="text-xs" style={{ color: "var(--green)" }}>
                {totalProtein.toLocaleString()}g protein
              </div>
            </div>
            <div className="card" style={{ flex: 1, minWidth: 160 }}>
              <div className="text-xs text-muted mb-2">SHOPPING PROGRESS</div>
              <div
                className="fw-700"
                style={{
                  fontSize: 22,
                  color:
                    checkedCount === shoppingList.length
                      ? "var(--green)"
                      : "var(--accent)",
                }}
              >
                {checkedCount} / {shoppingList.length}
              </div>
              {totalIngredientItems > 0 && (
                <div className="text-xs" style={{ color: "var(--text2)" }}>
                  Ingredients: {checkedIngredientCount} / {totalIngredientItems}
                </div>
              )}
              <div
                style={{
                  height: 6,
                  background: "var(--bg3)",
                  borderRadius: 3,
                  marginTop: 6,
                }}
              >
                <div
                  style={{
                    height: 6,
                    borderRadius: 3,
                    background:
                      checkedCount === shoppingList.length
                        ? "var(--green)"
                        : "var(--accent)",
                    width: `${shoppingList.length > 0 ? (checkedCount / shoppingList.length) * 100 : 0}%`,
                    transition: "width 0.3s",
                  }}
                />
              </div>
              {totalIngredientItems > 0 && (
                <div
                  style={{
                    height: 4,
                    background: "var(--bg3)",
                    borderRadius: 2,
                    marginTop: 6,
                  }}
                >
                  <div
                    style={{
                      height: 4,
                      borderRadius: 2,
                      background:
                        checkedIngredientCount === totalIngredientItems
                          ? "var(--green)"
                          : "var(--yellow)",
                      width: `${totalIngredientItems > 0 ? (checkedIngredientCount / totalIngredientItems) * 100 : 0}%`,
                      transition: "width 0.3s",
                    }}
                  />
                </div>
              )}
              {checkedCount === shoppingList.length &&
                shoppingList.length > 0 && (
                  <div
                    className="text-xs mt-2"
                    style={{ color: "var(--green)", fontWeight: 600 }}
                  >
                    ✓ All recipes shopped!
                  </div>
                )}
            </div>
          </div>

          {/* Store cost comparison */}
          {hasAnyIngredients && (
            <div className="mb-4">
              <div
                className="text-xs text-muted mb-2"
                style={{
                  textTransform: "uppercase",
                  letterSpacing: "0.5px",
                  fontWeight: 600,
                }}
              >
                Estimated Cost by Store (recipes with ingredient data)
              </div>
              <div className="store-totals">
                {STORE_LOCATIONS.map((store) => (
                  <div
                    key={store.id}
                    className={`store-total-card ${selectedStore === store.id ? "card" : ""}`}
                    style={
                      selectedStore === store.id
                        ? { borderColor: "var(--accent)" }
                        : { cursor: "pointer" }
                    }
                    onClick={() => setSelectedStore(store.id)}
                  >
                    <div className="st-name">{store.name}</div>
                    <div
                      className="st-price"
                      style={{
                        color:
                          store.chain === "kroger"
                            ? "var(--kroger)"
                            : "var(--sams)",
                      }}
                    >
                      ${storeTotals[store.id].toFixed(2)}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Store selector for detail view */}
          <div
            className="flex gap-3 mb-3"
            style={{ flexWrap: "wrap", alignItems: "center" }}
          >
            <span className="text-xs text-muted">Show prices for:</span>
            <select
              className="input"
              style={{ maxWidth: 220 }}
              value={selectedStore}
              onChange={(e) => setSelectedStore(e.target.value)}
            >
              {STORE_LOCATIONS.map((s) => (
                <option key={s.id} value={s.id}>
                  {s.name}
                </option>
              ))}
            </select>
            <span className="text-xs text-muted" style={{ marginLeft: "auto" }}>
              ▶ Use the arrow to expand ingredient breakdown and check off items
              (Buy or Addressed)
            </span>
          </div>

          <div className="table-wrap">
            <table>
              <thead>
                <tr>
                  <th>✓</th>
                  <th>Recipe</th>
                  <th>Book</th>
                  <th>Batches</th>
                  <th>Servings</th>
                  <th>Cal/Srv</th>
                  <th>Macros/Srv</th>
                  <th>Est. Cost</th>
                  <th>Source</th>
                </tr>
              </thead>
              <tbody>
                {shoppingList.map((item, i) => {
                  const ingredients = INGREDIENT_DB[item.recipeId];
                  const hasIng = !!ingredients;
                  const isExp = expanded[item.recipeId];
                  const recipeCost = hasIng
                    ? ingredients.reduce((sum, ing) => {
                        return (
                          sum +
                          (ingredientCost(
                            { ...ing, qty: ing.qty * item.batchesNeeded },
                            selectedStore,
                          ) || 0)
                        );
                      }, 0)
                    : null;

                  return (
                    <React.Fragment key={i}>
                      {/* Recipe row */}
                      <tr
                        className={hasIng ? "ing-row" : ""}
                        style={
                          checked[item.recipeId]
                            ? { opacity: 0.45, textDecoration: "line-through" }
                            : {}
                        }
                      >
                        <td>
                          <input
                            type="checkbox"
                            checked={checked[item.recipeId] || false}
                            onChange={() =>
                              toggleRecipeCheck(item, ingredients)
                            }
                          />
                        </td>
                        <td className="fw-600">
                          {hasIng && (
                            <button
                              type="button"
                              className={`ing-toggle ${isExp ? "open" : ""}`}
                              onClick={() => toggleExpand(item.recipeId)}
                              style={{
                                background: "transparent",
                                border: "none",
                                padding: 0,
                                marginRight: 6,
                                cursor: "pointer",
                              }}
                              aria-label={
                                isExp
                                  ? `Collapse ${item.name} ingredients`
                                  : `Expand ${item.name} ingredients`
                              }
                            >
                              ▶
                            </button>
                          )}
                          {item.name}
                          {hasIng && (
                            <span
                              className="text-xs text-muted"
                              style={{ marginLeft: 6 }}
                            >
                              ({ingredients.length} items)
                            </span>
                          )}
                        </td>
                        <td>
                          <span className="tag">
                            {item.book === "sc" ? "Slow Cooker" : "Meal Prep"}
                          </span>
                        </td>
                        <td>
                          {item.batchesNeeded}× ({item.servingsPerBatch}/batch)
                        </td>
                        <td style={{ fontWeight: 600 }}>
                          {item.totalServings}
                        </td>
                        <td style={{ color: "var(--yellow)" }}>
                          {item.calories}
                        </td>
                        <td className="text-xs">
                          <span style={{ color: "var(--green)" }}>
                            P{item.protein}
                          </span>{" "}
                          <span style={{ color: "var(--orange)" }}>
                            C
                            {recipes.find((r) => r.id === item.recipeId)
                              ?.carbs || 0}
                          </span>{" "}
                          <span style={{ color: "var(--red)" }}>
                            F
                            {recipes.find((r) => r.id === item.recipeId)?.fat ||
                              0}
                          </span>
                        </td>
                        <td
                          style={{
                            fontWeight: 600,
                            color:
                              recipeCost !== null
                                ? STORE_LOCATIONS.find(
                                    (s) => s.id === selectedStore,
                                  )?.chain === "kroger"
                                  ? "var(--kroger)"
                                  : "var(--sams)"
                                : "var(--text2)",
                          }}
                        >
                          {recipeCost !== null
                            ? `$${recipeCost.toFixed(2)}`
                            : "—"}
                        </td>
                        <td>
                          <button
                            className="btn btn-sm btn-outline"
                            onClick={() => openSourceViewer(item.recipeId)}
                          >
                            📄 p.{item.page}
                          </button>
                        </td>
                      </tr>

                      {/* Expanded ingredient rows */}
                      {hasIng &&
                        isExp &&
                        ingredients.map((ing, j) => {
                          const scaledQty = ing.qty * item.batchesNeeded;
                          const alts = getAltUnits(scaledQty, ing.unit);
                          const key = ingredientKey(item.recipeId, j);
                          const isBought = checkedIngredients[key] || false;
                          const isAddressed =
                            addressedIngredients[key] || false;
                          const isDone = isBought || isAddressed;
                          const cost = ingredientCost(
                            { ...ing, qty: scaledQty },
                            selectedStore,
                          );
                          return (
                            <tr key={`${i}-ing-${j}`} className="ing-detail">
                              <td>
                                <div
                                  style={{
                                    display: "flex",
                                    flexDirection: "column",
                                    gap: 4,
                                  }}
                                >
                                  <label
                                    className="text-xs"
                                    style={{
                                      display: "flex",
                                      alignItems: "center",
                                      gap: 4,
                                    }}
                                  >
                                    <input
                                      type="checkbox"
                                      checked={isBought}
                                      onChange={() =>
                                        toggleIngredientCheck(item.recipeId, j)
                                      }
                                      aria-label={`Mark ${ing.name} as purchased`}
                                    />
                                    Buy
                                  </label>
                                  <label
                                    className="text-xs text-muted"
                                    style={{
                                      display: "flex",
                                      alignItems: "center",
                                      gap: 4,
                                    }}
                                  >
                                    <input
                                      type="checkbox"
                                      checked={isAddressed}
                                      onChange={() =>
                                        toggleIngredientAddressed(
                                          item.recipeId,
                                          j,
                                        )
                                      }
                                      aria-label={`Mark ${ing.name} as addressed without purchase`}
                                    />
                                    Addressed
                                  </label>
                                </div>
                              </td>
                              <td colSpan={2} style={{ paddingLeft: 32 }}>
                                <div
                                  className="fw-600"
                                  style={
                                    isDone
                                      ? {
                                          textDecoration: "line-through",
                                          opacity: 0.6,
                                        }
                                      : {}
                                  }
                                >
                                  {ing.name}
                                </div>
                                <span className="tag" style={{ fontSize: 10 }}>
                                  {ing.cat}
                                </span>
                              </td>
                              <td colSpan={2}>
                                <div>
                                  <strong>{fmtAmt(scaledQty)}</strong>{" "}
                                  {ing.unit}
                                  {item.batchesNeeded > 1 && (
                                    <span className="text-xs text-muted">
                                      {" "}
                                      ({fmtAmt(ing.qty)} × {item.batchesNeeded})
                                    </span>
                                  )}
                                </div>
                                {alts.length > 0 && (
                                  <div className="alt-units">
                                    {alts.map((a, k) => (
                                      <span key={k}>
                                        {k > 0 ? " · " : ""}
                                        {fmtAmt(a.qty)} {a.unit}
                                      </span>
                                    ))}
                                  </div>
                                )}
                              </td>
                              <td colSpan={2}>
                                <div className="text-xs text-muted">
                                  {STORE_LOCATIONS.map((s, si) => {
                                    const c = ingredientCost(
                                      { ...ing, qty: scaledQty },
                                      s.id,
                                    );
                                    return c !== null ? (
                                      <span
                                        key={s.id}
                                        style={{
                                          marginRight: 8,
                                          color:
                                            s.id === selectedStore
                                              ? s.chain === "kroger"
                                                ? "var(--kroger)"
                                                : "var(--sams)"
                                              : "var(--text2)",
                                        }}
                                      >
                                        {s.area}: ${c.toFixed(2)}
                                      </span>
                                    ) : null;
                                  })}
                                </div>
                              </td>
                              <td
                                style={{
                                  fontWeight: 600,
                                  color:
                                    STORE_LOCATIONS.find(
                                      (s) => s.id === selectedStore,
                                    )?.chain === "kroger"
                                      ? "var(--kroger)"
                                      : "var(--sams)",
                                }}
                              >
                                {cost !== null ? `$${cost.toFixed(2)}` : "—"}
                              </td>
                              <td></td>
                            </tr>
                          );
                        })}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          </div>
        </>
      )}
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// PANTRY PAGE — Track home stock inventory
// ═════════════════════════════════════════════════════════════════════════
// Simple CRUD for pantry items with name/amount/unit. Items here show up as
// "In Pantry" on shopping list matches (currently matched by name comparison
// when ingredient-level parsing is added in a future release).
//
// Pre-seeded with common staples: olive oil, salt, pepper, garlic powder,
// cumin, soy sauce.
// ═════════════════════════════════════════════════════════════════════════
function PantryPage({ pantry, addPantryItem, removePantryItem }) {
  const [newName, setNewName] = useState("");
  const [newAmt, setNewAmt] = useState("1");
  const [newUnit, setNewUnit] = useState("ct");

  const handleAdd = () => {
    if (!newName.trim()) return;
    addPantryItem(newName.trim(), parseFloat(newAmt) || 1, newUnit);
    setNewName("");
    setNewAmt("1");
  };

  return (
    <div>
      <h1 className="section-title">Pantry Manager</h1>
      <p className="section-sub">
        Track what you have at home — items here are deducted from your shopping
        list.
      </p>

      <div className="card mb-4">
        <div className="fw-700 mb-2">Add Pantry Item</div>
        <div className="flex gap-2 items-center" style={{ flexWrap: "wrap" }}>
          <input
            className="input"
            style={{ flex: 2, minWidth: 160 }}
            placeholder="Item name…"
            value={newName}
            onChange={(e) => setNewName(e.target.value)}
            onKeyDown={(e) => e.key === "Enter" && handleAdd()}
          />
          <input
            className="input"
            style={{ flex: 1, minWidth: 60, maxWidth: 80 }}
            type="number"
            min="0"
            step="0.5"
            value={newAmt}
            onChange={(e) => setNewAmt(e.target.value)}
          />
          <select
            className="input"
            style={{ flex: 1, minWidth: 80, maxWidth: 120 }}
            value={newUnit}
            onChange={(e) => setNewUnit(e.target.value)}
          >
            {[
              "ct",
              "bottle",
              "box",
              "jar",
              "bag",
              "lb",
              "oz",
              "cup",
              "can",
              "bunch",
              "head",
              "tsp",
              "tbsp",
              "g",
              "kg",
              "ml",
              "l",
            ].map((u) => (
              <option key={u} value={u}>
                {u}
              </option>
            ))}
          </select>
          <button className="btn btn-primary btn-sm" onClick={handleAdd}>
            + Add
          </button>
        </div>
      </div>

      {pantry.length === 0 ? (
        <div className="empty-state">
          <div className="es-icon">🏠</div>Your pantry is empty. Add items you
          have at home.
        </div>
      ) : (
        <div className="pantry-grid">
          {pantry.map((item, i) => (
            <div key={i} className="card pantry-item">
              <div className="pi-name fw-600">{item.name}</div>
              <div className="pi-amt">
                {item.amt} {item.unit}
              </div>
              <button
                className="btn btn-sm btn-danger"
                onClick={() => removePantryItem(i)}
              >
                ✕
              </button>
            </div>
          ))}
        </div>
      )}

      <div className="divider" />
      <div className="text-sm text-muted">
        💡 Common pantry staples to consider: salt, black pepper, olive oil,
        garlic powder, onion powder, cumin, chili powder, oregano, soy sauce,
        flour, sugar, butter, eggs, cooking spray.
      </div>
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// RATINGS PAGE — Rate eaten meals, get tag-weighted recommendations
// ═════════════════════════════════════════════════════════════════════════
// Three sections:
//   1. Your Rated Recipes (1-5 stars) — already reviewed
//   2. Recommendations — sorted by match score (based on tag weights from
//      your ratings). Shows % match + a fill bar.
//   3. Unrated Recipes — queue to rate next
//
// Recommendation algorithm (see App's `recommendations` useMemo):
//   - Build tag weight map: for each rated recipe, distribute the rating
//     across all its tags (weight[tag] += rating; count[tag] += 1)
//   - For each recipe, average the weights of its tags → normalized score
//   - Unrated recipes default to 50% (neutral) when no ratings exist
// ═════════════════════════════════════════════════════════════════════════
function RatingsPage({
  recipes,
  ratings,
  setRating,
  recommendations,
  openSourceViewer,
}) {
  const ratedRecipes = recipes.filter((r) => ratings[r.id] > 0);
  const unratedRecipes = recipes.filter((r) => !ratings[r.id]);

  return (
    <div>
      <h1 className="section-title">Ratings & Recommendations</h1>
      <p className="section-sub">
        Rate your meals to get personalized recommendations weighted by your
        taste profile.
      </p>

      {ratedRecipes.length > 0 && (
        <div className="mb-4">
          <h3 className="fw-700 mb-2">
            Your Rated Recipes ({ratedRecipes.length})
          </h3>
          <div className="recipe-grid">
            {ratedRecipes.map((r) => (
              <div
                key={r.id}
                className="card recipe-card"
                style={{ cursor: "default" }}
              >
                <div className="flex items-center justify-between mb-2">
                  <div
                    className="rc-title"
                    style={{ marginBottom: 0, paddingRight: 0 }}
                  >
                    {r.title}
                  </div>
                  <button
                    className="btn btn-sm btn-outline"
                    onClick={() => openSourceViewer(r.id)}
                  >
                    📄
                  </button>
                </div>
                <div className="rc-macros">
                  <span className="macro-lg cal-total">
                    🔥 {r.calories} cal
                  </span>
                  <span className="macro-lg protein">P {r.protein}g</span>
                  <span className="macro-lg carbs">C {r.carbs}g</span>
                  <span className="macro-lg fat">F {r.fat}g</span>
                </div>
                <Stars
                  value={ratings[r.id]}
                  onChange={(v) => setRating(r.id, v)}
                />
                <div className="rc-tags mt-2">
                  {r.tags.slice(0, 3).map((t) => (
                    <span key={t} className="tag">
                      {t}
                    </span>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      <div className="divider" />

      <h3 className="fw-700 mb-2">Recommendations</h3>
      <p className="text-sm text-muted mb-3">
        {ratedRecipes.length === 0
          ? "Rate some recipes above to get personalized recommendations!"
          : "Based on your ratings, here are recipes you might enjoy:"}
      </p>

      <div className="recipe-grid">
        {recommendations.slice(0, 12).map((r) => (
          <div
            key={r.id}
            className="card recipe-card"
            style={{ cursor: "default" }}
          >
            <div className="flex items-center justify-between mb-2">
              <div
                className="rc-title truncate"
                style={{ marginBottom: 0, flex: 1, paddingRight: 8 }}
              >
                {r.title}
              </div>
              <span
                className="badge badge-accent"
                style={{ whiteSpace: "nowrap" }}
              >
                {r.score}% match
              </span>
            </div>
            <div className="rc-macros">
              <span className="macro-lg cal-total">🔥 {r.calories} cal</span>
              <span className="macro-lg protein">P {r.protein}g</span>
              <span className="macro-lg carbs">C {r.carbs}g</span>
              <span className="macro-lg fat">F {r.fat}g</span>
            </div>
            <div className="flex items-center gap-2 mb-2">
              <div className="rec-bar">
                <div
                  className="rec-bar-fill"
                  style={{ width: `${r.score}%` }}
                />
              </div>
            </div>
            <div className="flex items-center justify-between">
              <Stars
                value={ratings[r.id] || 0}
                onChange={(v) => setRating(r.id, v)}
              />
              <button
                className="btn btn-sm btn-outline"
                onClick={() => openSourceViewer(r.id)}
              >
                📄
              </button>
            </div>
            <div className="rc-tags mt-2">
              {r.tags.slice(0, 3).map((t) => (
                <span key={t} className="tag">
                  {t}
                </span>
              ))}
            </div>
          </div>
        ))}
      </div>

      {unratedRecipes.length > 0 && (
        <>
          <div className="divider" />
          <h3 className="fw-700 mb-2">
            Unrated Recipes ({unratedRecipes.length})
          </h3>
          <div className="recipe-grid">
            {unratedRecipes.slice(0, 8).map((r) => (
              <div key={r.id} className="card">
                <div className="fw-700 text-sm mb-2">{r.title}</div>
                <Stars value={0} onChange={(v) => setRating(r.id, v)} />
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// SOURCE VIEWER OVERLAY — Global modal accessible from any page
// ═════════════════════════════════════════════════════════════════════════
// Opens as a full-screen modal (z-index: 300) above the current page, which
// remains unchanged underneath. Closing returns the user exactly to their
// prior page state — critical for the "check a detail in the shopping list"
// workflow where the user doesn't want to lose their position.
//
// Image path convention: ./recipe_pages/recipe_{book}_{page}.jpg
//   e.g. recipe_sc_22.jpg, recipe_mp_16.jpg, recipe_v4_5.jpg
//
// Controls:
//   - Dropdown: switch between any of the 198 recipes
//   - Zoom in/out buttons (0.5x to 3.0x)
//   - ✕ button + Escape key + backdrop click all close
//   - Graceful fallback if image missing (shows expected path)
//
// The FAB button in App.render always opens this overlay. The overlay state
// (svOpen, svRecipeId) lives in App so it persists across page navigation.
// ═════════════════════════════════════════════════════════════════════════
function SourceViewerOverlay({ recipes, recipeId, setRecipeId, onClose }) {
  const [imgError, setImgError] = useState(false);
  const [zoom, setZoom] = useState(1);

  const recipe = recipeId ? recipes.find((r) => r.id === recipeId) : null;

  // Reset error state when recipe changes
  useEffect(() => {
    setImgError(false);
    setZoom(1);
  }, [recipeId]);

  // Close on Escape key
  useEffect(() => {
    const handler = (e) => {
      if (e.key === "Escape") onClose();
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [onClose]);

  const imgPath = recipe
    ? `./recipe_pages/recipe_${recipe.book}_${recipe.page}.jpg`
    : null;

  return (
    <div
      className="sv-overlay"
      onClick={(e) => {
        if (e.target === e.currentTarget) onClose();
      }}
    >
      <div className="sv-modal">
        <div className="sv-header">
          <h3>📄 Source Recipe Viewer</h3>
          <div className="flex gap-2 items-center">
            {recipe && (
              <>
                <button
                  className="btn btn-sm btn-outline"
                  onClick={() => setZoom((z) => Math.max(0.5, z - 0.25))}
                >
                  −
                </button>
                <span className="text-xs text-muted">
                  {Math.round(zoom * 100)}%
                </span>
                <button
                  className="btn btn-sm btn-outline"
                  onClick={() => setZoom((z) => Math.min(3, z + 0.25))}
                >
                  +
                </button>
              </>
            )}
            <button className="sv-close" onClick={onClose}>
              ✕
            </button>
          </div>
        </div>
        <div className="sv-body">
          <div className="sv-select">
            <select
              className="input"
              style={{ flex: 1 }}
              value={recipeId || ""}
              onChange={(e) => setRecipeId(+e.target.value || null)}
            >
              <option value="">Select a recipe to view source…</option>
              {recipes.map((r) => (
                <option key={r.id} value={r.id}>
                  {r.title} ({r.book === "sc" ? "Slow Cooker" : "Meal Prep"} p.
                  {r.page})
                </option>
              ))}
            </select>
          </div>

          {!recipe ? (
            <div className="sv-placeholder">
              <div className="sv-icon">📄</div>
              <div>Select a recipe above to view the original source page</div>
              <div className="text-xs text-muted">
                Images load from ./recipe_pages/ folder
              </div>
            </div>
          ) : imgError ? (
            <div className="sv-placeholder">
              <div className="sv-icon">⚠️</div>
              <div className="fw-700">{recipe.title}</div>
              <div className="text-sm text-muted">
                Source image not found at:
                <br />
                <code style={{ fontSize: 11, color: "var(--accent2)" }}>
                  {imgPath}
                </code>
              </div>
              <div className="text-xs text-muted" style={{ maxWidth: 400 }}>
                Make sure the recipe_pages/ folder is in the same directory as
                this HTML file. Images should be named: recipe_sc_12.jpg or
                recipe_mp_8.jpg
              </div>
            </div>
          ) : (
            <div className="sv-img-wrap">
              <img
                src={imgPath}
                alt={`Source: ${recipe.title}`}
                style={{
                  transform: `scale(${zoom})`,
                  transformOrigin: "top center",
                  transition: "transform 0.2s",
                }}
                onError={() => setImgError(true)}
              />
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// SCALE MODAL — Shown for recipes with servings < 10
// ═════════════════════════════════════════════════════════════════════════
// User sets target servings (min 10); displays the scale factor and total
// batch nutrition (calories × target, protein × target, etc). Per-serving
// macros remain unchanged — scaling only affects the batch totals.
// ═════════════════════════════════════════════════════════════════════════
function ScaleModal({ recipe, onClose }) {
  const [target, setTarget] = useState(10);
  if (!recipe) return null;
  const scale = target / recipe.servings;

  return (
    <div
      className="scale-overlay"
      onClick={(e) => {
        if (e.target === e.currentTarget) onClose();
      }}
    >
      <div className="scale-modal">
        <div className="flex items-center justify-between mb-3">
          <h3 className="fw-700">Scale: {recipe.title}</h3>
          <button className="sv-close" onClick={onClose}>
            ✕
          </button>
        </div>
        <div className="text-sm text-muted mb-3">
          Original: {recipe.servings} servings · Scale factor:{" "}
          {scale.toFixed(2)}x
        </div>
        <div className="flex gap-2 items-center mb-3">
          <span className="text-sm fw-600">Target servings:</span>
          <input
            type="number"
            className="input"
            style={{ width: 80 }}
            min={10}
            value={target}
            onChange={(e) => setTarget(Math.max(10, +e.target.value || 10))}
          />
        </div>
        <div className="table-wrap">
          <div
            className="card"
            style={{
              background: "var(--bg3)",
              textAlign: "center",
              padding: "20px",
            }}
          >
            <div style={{ fontSize: 32, marginBottom: 8 }}>📄</div>
            <div className="fw-700 mb-2">
              View source recipe page for full ingredient list
            </div>
            <div className="text-xs text-muted mb-3">
              {recipe.book === "sc" ? "Slow Cooker" : "Meal Prep"} Cookbook —
              page {recipe.page}
            </div>
            <div className="text-sm text-muted">
              Multiply all ingredient amounts by{" "}
              <strong style={{ color: "var(--accent2)" }}>
                {scale.toFixed(2)}×
              </strong>{" "}
              to get {target} servings
            </div>
          </div>
        </div>
        <div className="mt-3 text-xs text-muted">
          Makes {target} servings ({Math.ceil(target / 2)} days at 2 meals/day)
        </div>
        <div className="mt-3">
          <div className="text-xs fw-600 mb-2" style={{ color: "var(--text)" }}>
            Nutrition per Serving (unchanged by scaling)
          </div>
          <div className="macro-strip">
            <span className="macro-chip protein">P {recipe.protein}g</span>
            <span className="macro-chip carbs">C {recipe.carbs}g</span>
            <span className="macro-chip fat">F {recipe.fat}g</span>
            <span className="macro-chip fiber">Fb {recipe.fiber}g</span>
            <span className="macro-chip cal-total">
              🔥 {recipe.calories} cal/srv
            </span>
          </div>
          <div className="text-xs text-muted mt-2">
            Total batch: {(recipe.calories * target).toLocaleString()} cal ·{" "}
            {recipe.protein * target}g protein · {recipe.carbs * target}g carbs
            · {recipe.fat * target}g fat
          </div>
        </div>
      </div>
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// MACRO COMPONENTS — Reusable nutrition display primitives
// ═════════════════════════════════════════════════════════════════════════
// MacroStrip: compact horizontal chips (P/C/F/Fb + optional total calories)
// MacroDetail: 5-card grid with big numbers + percentage-of-calories breakdown
// Color convention (consistent across app):
//   yellow = calories, green = protein, orange = carbs, red = fat, cyan = fiber
// ═════════════════════════════════════════════════════════════════════════
function MacroStrip({ r, showTotal = false }) {
  return (
    <div className="macro-strip">
      <span className="macro-chip protein">P {r.protein}g</span>
      <span className="macro-chip carbs">C {r.carbs}g</span>
      <span className="macro-chip fat">F {r.fat}g</span>
      <span className="macro-chip fiber">Fb {r.fiber}g</span>
      {showTotal && (
        <span className="macro-chip cal-total">
          🔥 {r.calories * r.servings} total cal
        </span>
      )}
    </div>
  );
}

function MacroDetail({ r }) {
  const totalCal = r.calories * r.servings;
  const pPct = Math.round(((r.protein * 4) / r.calories) * 100);
  const cPct = Math.round(((r.carbs * 4) / r.calories) * 100);
  const fPct = Math.round(((r.fat * 9) / r.calories) * 100);
  return (
    <div className="macro-detail-grid">
      <div className="macro-detail-card calories">
        <div className="md-val">{r.calories}</div>
        <div className="md-label">cal/serving</div>
        <div className="md-sub">{totalCal.toLocaleString()} total</div>
      </div>
      <div className="macro-detail-card protein">
        <div className="md-val">{r.protein}g</div>
        <div className="md-label">Protein</div>
        <div className="md-sub">{pPct}% of cal</div>
      </div>
      <div className="macro-detail-card carbs">
        <div className="md-val">{r.carbs}g</div>
        <div className="md-label">Carbs</div>
        <div className="md-sub">{cPct}% of cal</div>
      </div>
      <div className="macro-detail-card fat">
        <div className="md-val">{r.fat}g</div>
        <div className="md-label">Fat</div>
        <div className="md-sub">{fPct}% of cal</div>
      </div>
      <div className="macro-detail-card fiber">
        <div className="md-val">{r.fiber}g</div>
        <div className="md-label">Fiber</div>
        <div className="md-sub">per serving</div>
      </div>
    </div>
  );
}

// ═════════════════════════════════════════════════════════════════════════
// STARS COMPONENT — 5-star rating widget
// ═════════════════════════════════════════════════════════════════════════
// Click a star to set rating; click the same star again to clear (toggle).
// Rating is 0-5. Yellow filled = rated, gray outline = unrated.
// ═════════════════════════════════════════════════════════════════════════
function Stars({ value, onChange }) {
  return (
    <div className="stars">
      {[1, 2, 3, 4, 5].map((n) => (
        <span
          key={n}
          className={`star ${n <= value ? "filled" : ""}`}
          onClick={() => onChange(n === value ? 0 : n)}
        >
          {n <= value ? "★" : "☆"}
        </span>
      ))}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(
  React.createElement(App),
);
