lokesh341 commited on
Commit
1757ef9
·
verified ·
1 Parent(s): 41845e5

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. templates/menu.html +375 -271
templates/menu.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
@@ -675,100 +675,201 @@
675
  50% { transform: scale(1.1); }
676
  100% { transform: scale(1); }
677
  }
678
- .highlight-item {
679
- animation: highlight 2s ease-out;
680
- }
681
- @keyframes highlight {
682
- 0% { background-color: #FFFFE0; }
683
- 100% { background-color: transparent; }
684
- }
685
- .loading-overlay {
686
- position: fixed;
687
- top: 0;
688
- left: 0;
689
- width: 100%;
690
- height: 100%;
691
- background-color: rgba(255,255,255,0.8);
692
- display: flex;
693
- justify-content: center;
694
- align-items: center;
695
- z-index: 2000;
696
- display: none;
697
- }
698
- .spinner {
699
- width: 40px;
700
- height: 40px;
701
- border: 4px solid #f3f3f3;
702
- border-top: 4px solid #0FAA39;
703
- border-radius: 50%;
704
- animation: spin 1s linear infinite;
705
- }
706
- @keyframes spin {
707
- 0% { transform: rotate(0deg); }
708
- 100% { transform: rotate(360deg); }
709
- }
710
- .search-result-modal {
711
- position: fixed;
712
- top: 20%;
713
- left: 50%;
714
- transform: translateX(-50%);
715
- width: 400px;
716
- max-width: 90%;
717
- background: white;
718
- border-radius: 15px;
719
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
720
- z-index: 2000;
721
- padding: 20px;
722
- display: none;
723
- }
724
- .search-result-modal.active {
725
- display: block;
726
- }
727
- .search-result-modal .item-info {
728
- text-align: center;
729
- margin-bottom: 15px;
730
- }
731
- .search-result-modal .item-info img {
732
- max-height: 100px;
733
- width: auto;
734
- border-radius: 8px;
735
- }
736
- .search-result-modal .quick-add {
737
- display: flex;
738
- justify-content: center;
739
- gap: 10px;
740
- }
741
- .search-result-modal .btn-quick-add {
742
- background-color: #0FAA39;
743
- color: white;
744
- padding: 8px 20px;
745
- border-radius: 5px;
746
- border: none;
747
- transition: background-color 0.3s ease;
748
- }
749
- .search-result-modal .btn-quick-add:hover {
750
- background-color: #0D9232;
751
- }
752
- .search-result-modal .btn-close {
753
- position: absolute;
754
- top: 10px;
755
- right: 10px;
756
- background: none;
757
- border: none;
758
- font-size: 20px;
759
- cursor: pointer;
760
- }
761
  @media (max-width: 576px) {
762
- /* Existing media queries remain unchanged */
763
- .search-result-modal {
764
- width: 300px;
765
- padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  }
767
- .search-result-modal .item-info img {
768
- max-height: 80px;
769
  }
770
- .search-result-modal .btn-quick-add {
771
- padding: 6px 15px;
772
  font-size: 14px;
773
  }
774
  }
@@ -845,7 +946,7 @@
845
  <h3>{{ section }}</h3>
846
  <div class="row">
847
  {% for item in items %}
848
- <div class="col-md-6 mb-4" data-item-name="{{ item.Name | default('Unnamed Item') | e }}">
849
  <div class="card menu-card">
850
  <video
851
  class="card-img-top menu-video"
@@ -900,7 +1001,7 @@
900
  <h6>Description</h6>
901
  <p>{{ item.Description__c | default('No description available') }}</p>
902
  <h6>Ingredients Info</h6>
903
- <p>{{ item.IngredientsInfo__c | default('Not specified') }}</p>
904
  <h6>Nutritional Info</h6>
905
  <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
906
  <h6>Allergens</h6>
@@ -1022,36 +1123,15 @@
1022
  <button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
1023
  </div>
1024
 
1025
- <!-- Loading Overlay -->
1026
- <div class="loading-overlay" id="loadingOverlay">
1027
- <div class="spinner"></div>
1028
- </div>
1029
-
1030
- <!-- Search Result Popup -->
1031
- <div class="search-result-modal" id="searchResultModal">
1032
- <button class="btn-close" id="closeSearchModal">×</button>
1033
- <div class="item-info" id="searchResultInfo"></div>
1034
- <div class="quick-add">
1035
- <button class="btn-quick-add" id="quickAddToCart">Add to Cart</button>
1036
- </div>
1037
- </div>
1038
-
1039
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
1040
  <script>
1041
  let isProcessingRequest = false;
1042
  let currentSoftDrinkButton = null;
1043
 
1044
- const menuItemsData = [
1045
  {% for section, items in ordered_menu.items() %}
1046
  {% for item in items %}
1047
- {
1048
- name: "{{ item.Name | default('Unnamed Item') | e }}",
1049
- price: "{{ item.Price__c | default('0.00') }}",
1050
- image: "{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}",
1051
- section: "{{ item.Section__c | default(section) | e }}",
1052
- category: "{{ selected_category | e }}",
1053
- description: "{{ item.Description__c | default('No description') | e }}"
1054
- },
1055
  {% endfor %}
1056
  {% endfor %}
1057
  ];
@@ -1072,6 +1152,7 @@
1072
  "Whole Wheat Flour", "Yogurt (Curd)"
1073
  ];
1074
 
 
1075
  function sanitizeInput(input) {
1076
  const div = document.createElement('div');
1077
  div.textContent = input;
@@ -1215,67 +1296,7 @@
1215
  }
1216
  }
1217
 
1218
- function showSearchResultModal(item) {
1219
- const modal = document.getElementById('searchResultModal');
1220
- const info = document.getElementById('searchResultInfo');
1221
- info.innerHTML = `
1222
- <img src="${item.image}" alt="${item.name}">
1223
- <h5>${item.name}</h5>
1224
- <p>$${item.price}</p>
1225
- <p>${item.description}</p>
1226
- `;
1227
- modal.classList.add('active');
1228
-
1229
- document.getElementById('quickAddToCart').onclick = () => {
1230
- quickAddToCart(item);
1231
- modal.classList.remove('active');
1232
- };
1233
- document.getElementById('closeSearchModal').onclick = () => {
1234
- modal.classList.remove('active');
1235
- };
1236
- }
1237
-
1238
- function quickAddToCart(item) {
1239
- const cartPayload = {
1240
- itemName: item.name,
1241
- itemPrice: parseFloat(item.price),
1242
- itemImage: item.image,
1243
- section: item.section,
1244
- category: item.category,
1245
- addons: [],
1246
- instructions: '',
1247
- quantity: 1
1248
- };
1249
-
1250
- fetch('/cart/add', {
1251
- method: 'POST',
1252
- headers: {
1253
- 'Content-Type': 'application/json',
1254
- },
1255
- body: JSON.stringify(cartPayload)
1256
- })
1257
- .then(response => response.json())
1258
- .then(data => {
1259
- if (data.success) {
1260
- updateCartUI(data.cart);
1261
- alert('Item added to cart successfully!');
1262
- } else {
1263
- const cart = addToCartLocalStorage(cartPayload);
1264
- updateCartUI(cart);
1265
- alert('Item added to cart (local storage)');
1266
- }
1267
- })
1268
- .catch(err => {
1269
- console.error('Error:', err);
1270
- const cart = addToCartLocalStorage(cartPayload);
1271
- updateCartUI(cart);
1272
- alert('Item added to cart (local storage)');
1273
- });
1274
- }
1275
-
1276
  document.addEventListener('DOMContentLoaded', function () {
1277
- handleHighlightedItem();
1278
-
1279
  const avatarContainer = document.querySelector('.avatar-dropdown-container');
1280
  const dropdownMenu = document.querySelector('.dropdown-menu');
1281
  const avatarIcon = document.getElementById('avatarIcon');
@@ -1283,6 +1304,7 @@
1283
  const deleteAvatar = document.getElementById('deleteAvatar');
1284
  const viewAvatar = document.getElementById('viewAvatar');
1285
 
 
1286
  function loadAvatar() {
1287
  const savedAvatar = localStorage.getItem('userAvatar');
1288
  const avatarImage = avatarIcon.querySelector('.avatar-image');
@@ -1301,6 +1323,7 @@
1301
  }
1302
  }
1303
 
 
1304
  function ensureAvatarOptions() {
1305
  if (!document.querySelector('#deleteAvatar')) {
1306
  const deleteItem = document.createElement('div');
@@ -1321,6 +1344,7 @@
1321
  }
1322
  }
1323
 
 
1324
  function removeAvatarOptions() {
1325
  const deleteItem = document.getElementById('deleteAvatar');
1326
  const viewItem = document.getElementById('viewAvatar');
@@ -1328,25 +1352,29 @@
1328
  if (viewItem) viewItem.remove();
1329
  }
1330
 
 
1331
  function saveAvatar(imageUrl) {
1332
  localStorage.setItem('userAvatar', imageUrl);
1333
  }
1334
 
 
1335
  function clearAvatar() {
1336
  localStorage.removeItem('userAvatar');
1337
  avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1338
  removeAvatarOptions();
1339
  }
1340
 
 
1341
  avatarIcon.addEventListener('click', function(event) {
1342
  event.stopPropagation();
1343
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1344
  });
1345
 
 
1346
  avatarUpload.addEventListener('change', function(event) {
1347
  const file = event.target.files[0];
1348
  if (file) {
1349
- const maxSize = 15 * 1024 * 1024;
1350
  if (file.size > maxSize) {
1351
  alert('Image size must not exceed 15MB.');
1352
  this.value = '';
@@ -1379,18 +1407,52 @@
1379
  ensureAvatarOptions();
1380
  dropdownMenu.style.display = 'none';
1381
  } else {
1382
- alert('Failed to upload image: ' + (data.error || 'Unknown error'));
 
 
 
 
 
 
 
 
 
 
1383
  }
1384
  })
1385
  .catch(error => {
1386
  console.error('Upload error:', error);
1387
- alert('Error uploading image.');
 
 
 
 
 
 
 
 
 
 
1388
  });
1389
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
1390
  reader.readAsDataURL(file);
1391
  }
1392
  });
1393
 
 
1394
  function addDeleteListener(deleteElement) {
1395
  deleteElement.addEventListener('click', function() {
1396
  fetch('/delete_avatar', {
@@ -1399,7 +1461,10 @@
1399
  'Content-Type': 'application/json'
1400
  }
1401
  })
1402
- .then(response => response.json())
 
 
 
1403
  .then(data => {
1404
  if (data.success) {
1405
  clearAvatar();
@@ -1410,11 +1475,12 @@
1410
  })
1411
  .catch(error => {
1412
  console.error('Delete error:', error);
1413
- alert('Error deleting image.');
1414
  });
1415
  });
1416
  }
1417
 
 
1418
  function addViewListener(viewElement) {
1419
  viewElement.addEventListener('click', function() {
1420
  const avatarImage = avatarIcon.querySelector('.avatar-image');
@@ -1423,16 +1489,21 @@
1423
  modalImg.src = avatarImage.src;
1424
  const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
1425
  modal.show();
 
 
1426
  }
1427
  dropdownMenu.style.display = 'none';
1428
  });
1429
  }
1430
 
 
1431
  if (deleteAvatar) addDeleteListener(deleteAvatar);
1432
  if (viewAvatar) addViewListener(viewAvatar);
1433
 
 
1434
  loadAvatar();
1435
 
 
1436
  document.addEventListener('click', function(event) {
1437
  if (!avatarContainer.contains(event.target)) {
1438
  dropdownMenu.style.display = 'none';
@@ -1441,27 +1512,37 @@
1441
 
1442
  const menuCards = document.querySelectorAll('.menu-card');
1443
  const menuVideos = document.querySelectorAll('.menu-video');
1444
- const cardObserver = new IntersectionObserver((entries) => {
1445
  entries.forEach(entry => {
1446
  if (entry.isIntersecting) {
1447
  entry.target.classList.add('visible');
 
1448
  }
1449
  });
1450
- }, { threshold: 0.1 });
1451
- const videoObserver = new IntersectionObserver((entries) => {
 
 
 
 
1452
  entries.forEach(entry => {
1453
  if (entry.isIntersecting) {
1454
  const video = entry.target;
1455
  const src = video.getAttribute('data-src');
1456
- if (src) {
1457
  const source = video.querySelector('source');
1458
  source.src = src;
1459
  video.load();
1460
  }
1461
  video.classList.add('loaded');
 
1462
  }
1463
  });
1464
- }, { rootMargin: '200px' });
 
 
 
 
1465
  menuCards.forEach(card => cardObserver.observe(card));
1466
  menuVideos.forEach(video => videoObserver.observe(video));
1467
 
@@ -1470,15 +1551,25 @@
1470
  link.addEventListener('click', function () {
1471
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1472
  const detailsDiv = document.getElementById(`details-${itemName}`);
1473
- const isShown = detailsDiv.classList.contains('show');
1474
- document.querySelectorAll('.item-details.show').forEach(other => {
1475
- if (other !== detailsDiv) {
1476
- other.classList.remove('show');
1477
- other.previousElementSibling.querySelector('.toggle-details').innerText = 'Show Details';
 
 
 
 
1478
  }
1479
  });
1480
- detailsDiv.classList.toggle('show');
1481
- this.innerText = isShown ? 'Show Details' : 'Hide Details';
 
 
 
 
 
 
1482
  });
1483
  });
1484
 
@@ -1500,59 +1591,35 @@
1500
 
1501
  const searchBar = document.getElementById('searchBar');
1502
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1503
- const debouncedSearch = debounce(handleSearch, 300);
1504
-
1505
  searchBar.addEventListener('input', function () {
1506
  const input = sanitizeInput(this.value.trim().toLowerCase());
1507
  suggestionsContainer.innerHTML = '';
1508
  suggestionsContainer.style.display = 'none';
1509
-
1510
- if (input.length >= 2) {
1511
- const filteredItems = menuItemsData.filter(item =>
1512
- item.name.toLowerCase().includes(input) ||
1513
- item.section.toLowerCase().includes(input) ||
1514
- item.description.toLowerCase().includes(input)
1515
  );
1516
-
1517
  if (filteredItems.length > 0) {
1518
- filteredItems.slice(0, 5).forEach(item => {
1519
  const suggestionDiv = document.createElement('div');
1520
  suggestionDiv.classList.add('suggestion-item');
1521
- suggestionDiv.innerHTML = `
1522
- <strong>${item.name}</strong><br>
1523
- <small>${item.section} - $${item.price}</small>
1524
- `;
1525
  suggestionDiv.addEventListener('click', function () {
1526
- searchBar.value = item.name;
1527
  suggestionsContainer.style.display = 'none';
1528
- showSearchResultModal(item);
1529
  });
1530
  suggestionsContainer.appendChild(suggestionDiv);
1531
  });
1532
  suggestionsContainer.style.display = 'block';
1533
  }
1534
  }
1535
- debouncedSearch(input);
1536
  });
1537
-
1538
- searchBar.addEventListener('keypress', function(e) {
1539
- if (e.key === 'Enter') {
1540
- const input = sanitizeInput(this.value.trim().toLowerCase());
1541
- const exactMatch = menuItemsData.find(item =>
1542
- item.name.toLowerCase() === input
1543
- );
1544
- if (exactMatch) {
1545
- showSearchResultModal(exactMatch);
1546
- }
1547
- }
1548
- });
1549
-
1550
  document.addEventListener('click', function (event) {
1551
- if (!searchBar.contains(event.target) &&
1552
- !suggestionsContainer.contains(event.target) &&
1553
- !document.getElementById('searchResultModal').contains(event.target)) {
1554
  suggestionsContainer.style.display = 'none';
1555
- document.getElementById('searchResultModal').classList.remove('active');
1556
  }
1557
  });
1558
 
@@ -1590,8 +1657,8 @@
1590
  suggestionDiv.innerText = ingredient;
1591
  suggestionDiv.addEventListener('click', function () {
1592
  const currentValue = descriptionTextarea.value;
1593
- const lastCommaIndex = currentValue.lastIndexOf(',');
1594
- const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
1595
  descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
1596
  descriptionSuggestions.style.display = 'none';
1597
  descriptionTextarea.focus();
@@ -1610,16 +1677,18 @@
1610
  });
1611
  }
1612
 
 
1613
  const customDishForm = document.getElementById('customDishForm');
1614
  if (customDishForm) {
1615
  customDishForm.addEventListener('submit', function(event) {
1616
- event.preventDefault();
1617
  const dishName = document.getElementById('custom-dish-name').value.trim();
1618
  const description = document.getElementById('custom-dish-description').value.trim();
1619
  if (!dishName || !description) {
1620
- alert('Please fill in both fields.');
 
1621
  return;
1622
  }
 
1623
  fetch('/customdish/generate_custom_dish', {
1624
  method: 'POST',
1625
  headers: {
@@ -1636,54 +1705,82 @@
1636
  alert('Custom dish submitted successfully!');
1637
  window.location.reload();
1638
  } else {
1639
- alert('Failed to submit: ' + (data.error || 'Unknown error'));
1640
  }
1641
  })
1642
  .catch(error => {
1643
- console.error('Error:', error);
1644
- alert('Error submitting custom dish.');
1645
  });
1646
  });
1647
  }
1648
 
1649
  fetch('/cart/get')
1650
- .then(response => response.json())
 
 
 
 
 
1651
  .then(data => {
1652
  if (data.success) {
1653
  updateCartUI(data.cart);
1654
  } else {
1655
- updateCartUI(getCartLocalStorage());
 
 
1656
  }
1657
  })
1658
  .catch(err => {
1659
  console.error('Error fetching cart:', err);
1660
- updateCartUI(getCartLocalStorage());
 
1661
  });
1662
 
 
 
 
 
 
 
 
1663
  const decreaseBtn = document.getElementById('decreaseQuantity');
1664
  const increaseBtn = document.getElementById('increaseQuantity');
1665
  const quantityInput = document.getElementById('quantityInput');
1666
  decreaseBtn.addEventListener('click', function () {
1667
- let qty = parseInt(quantityInput.value);
1668
- if (qty > 1) quantityInput.value = qty - 1;
 
 
 
1669
  });
1670
  increaseBtn.addEventListener('click', function () {
1671
- let qty = parseInt(quantityInput.value);
1672
- quantityInput.value = qty + 1;
 
1673
  });
1674
 
1675
  const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
1676
  const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
1677
  const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
 
1678
  softDrinkDecreaseBtn.addEventListener('click', function() {
1679
- let qty = parseInt(softDrinkQuantityInput.value);
1680
- if (qty > 1) softDrinkQuantityInput.value = qty - 1;
 
 
 
1681
  });
 
1682
  softDrinkIncreaseBtn.addEventListener('click', function() {
1683
- let qty = parseInt(softDrinkQuantityInput.value);
1684
- if (qty < 1000) softDrinkQuantityInput.value = qty + 1;
 
 
 
1685
  });
1686
 
 
1687
  const micIcon = document.getElementById('micIcon');
1688
  const micUnsupported = document.getElementById('micUnsupported');
1689
  const micPopup = document.getElementById('micPopup');
@@ -1706,28 +1803,42 @@
1706
  recognition.onresult = (event) => {
1707
  let interimTranscript = '';
1708
  let finalTranscript = '';
 
1709
  for (let i = event.resultIndex; i < event.results.length; i++) {
1710
  const transcript = event.results[i][0].transcript;
1711
- if (event.results[i].isFinal) finalTranscript += transcript;
1712
- else interimTranscript += transcript;
 
 
 
 
 
 
 
1713
  }
1714
- if (interimTranscript) micPopupText.textContent = interimTranscript;
1715
  if (finalTranscript) {
1716
  searchBar.value = sanitizeInput(finalTranscript.trim());
1717
- debouncedSearch(finalTranscript.trim().toLowerCase());
1718
  micPopup.classList.remove('active');
1719
  }
1720
  };
1721
 
1722
  recognition.onend = () => {
1723
  micIcon.classList.remove('active');
1724
- setTimeout(() => micPopup.classList.remove('active'), 1000);
 
 
 
 
1725
  };
1726
 
1727
  recognition.onerror = (event) => {
1728
  micIcon.classList.remove('active');
1729
  micPopupText.textContent = 'Error: ' + event.error;
1730
- setTimeout(() => micPopup.classList.remove('active'), 2000);
 
 
1731
  console.error('Speech error:', event.error);
1732
  };
1733
 
@@ -1736,8 +1847,10 @@
1736
  recognition.start();
1737
  } catch (e) {
1738
  micPopupText.textContent = 'Error starting microphone';
1739
- setTimeout(() => micPopup.classList.remove('active'), 2000);
1740
- console.error('Error:', e);
 
 
1741
  }
1742
  });
1743
 
@@ -1752,23 +1865,15 @@
1752
  }
1753
  });
1754
 
1755
- window.addEventListener('load', function() {
1756
- document.getElementById('loadingOverlay').style.display = 'none';
1757
- });
1758
-
1759
- function handleSearch(input) {
1760
  const sections = document.querySelectorAll('h3');
1761
  const items = document.querySelectorAll('.menu-card');
1762
  let matchedSections = new Set();
1763
-
1764
  items.forEach(item => {
1765
  const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1766
  const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1767
- const itemDescription = item.querySelector('.item-details p')?.innerText.toLowerCase() || '';
1768
-
1769
- if (itemName.includes(input) ||
1770
- itemSection.includes(input) ||
1771
- itemDescription.includes(input)) {
1772
  item.style.display = 'block';
1773
  item.classList.add('visible');
1774
  matchedSections.add(item.closest('.row'));
@@ -1776,7 +1881,6 @@
1776
  item.style.display = 'none';
1777
  }
1778
  });
1779
-
1780
  sections.forEach(section => {
1781
  const sectionRow = section.nextElementSibling;
1782
  if (matchedSections.has(sectionRow)) {
@@ -1787,7 +1891,6 @@
1787
  sectionRow.style.display = 'none';
1788
  }
1789
  });
1790
-
1791
  if (!input) {
1792
  sections.forEach(section => {
1793
  section.style.display = 'block';
@@ -1799,10 +1902,12 @@
1799
  });
1800
  }
1801
  }
1802
- function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
 
1803
  document.getElementById('modal-name').innerText = name;
1804
  document.getElementById('modal-price').innerText = `$${price}`;
1805
- document.getElementById('modal-img').src = image || '/static/placeholder.jpg';
 
1806
  document.getElementById('modal-description').innerText = description || 'No description available.';
1807
  document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
1808
  document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
@@ -1852,8 +1957,7 @@
1852
  document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
1853
  });
1854
  }
1855
-
1856
- document.addEventListener('click', function(event) {
1857
  if (event.target.classList.contains('addon-option')) {
1858
  handleAddonClick(event.target);
1859
  }
 
1
+ <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
 
675
  50% { transform: scale(1.1); }
676
  100% { transform: scale(1); }
677
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  @media (max-width: 576px) {
679
+ .fixed-top-bar {
680
+ height: 60px;
681
+ padding: 10px;
682
+ }
683
+ .search-bar-container {
684
+ width: 80%;
685
+ max-width: 100%;
686
+ left: 10px;
687
+ top: 50%;
688
+ transform: translateY(-50%);
689
+ }
690
+ .search-bar-container input {
691
+ padding: 6px 35px 6px 35px;
692
+ font-size: 14px;
693
+ border-radius: 20px;
694
+ }
695
+ .search-icon {
696
+ left: 12px;
697
+ font-size: 16px;
698
+ }
699
+ .mic-icon {
700
+ right: 12px;
701
+ font-size: 16px;
702
+ }
703
+ .avatar-dropdown-container {
704
+ right: 10px;
705
+ }
706
+ .avatar-icon {
707
+ width: 40px;
708
+ height: 40px;
709
+ font-size: 20px;
710
+ }
711
+ .dropdown-menu {
712
+ width: 180px;
713
+ }
714
+ .dropdown-menu .dropdown-item {
715
+ padding: 8px 12px;
716
+ font-size: 13px;
717
+ }
718
+ .upload-item,
719
+ .delete-item,
720
+ .view-item {
721
+ padding: 8px 12px;
722
+ font-size: 13px;
723
+ }
724
+ .category-buttons {
725
+ gap: 8px;
726
+ }
727
+ .category-button {
728
+ padding: 4px 12px;
729
+ font-size: 0.85rem;
730
+ }
731
+ .modal-dialog {
732
+ max-width: 96%;
733
+ margin: 5px auto;
734
+ }
735
+ .modal-header {
736
+ padding: 5px 10px;
737
+ }
738
+ .modal-title {
739
+ font-size: 14px;
740
+ }
741
+ .modal-body {
742
+ max-height: 50vh;
743
+ padding: 8px;
744
+ }
745
+ .modal-body #avatar-modal-img {
746
+ max-height: 300px;
747
+ }
748
+ .modal-body #modal-img {
749
+ max-height: 150px;
750
+ width: 100%;
751
+ max-width: 150px;
752
+ margin: 0 auto 5px;
753
+ display: block;
754
+ }
755
+ .modal-body #modal-name {
756
+ font-size: 18px;
757
+ margin-bottom: 3px;
758
+ }
759
+ .modal-body #modal-price {
760
+ font-size: 14px;
761
+ margin-bottom: 5px;
762
+ }
763
+ .modal-body #modal-description {
764
+ font-size: 12px;
765
+ margin-bottom: 5px;
766
+ }
767
+ .modal-body .nutritional-info {
768
+ font-size: 10px;
769
+ margin-bottom: 5px;
770
+ }
771
+ .modal-body #modal-addons h6,
772
+ .modal-body #first-row h6 {
773
+ font-size: 12px;
774
+ margin-bottom: 5px;
775
+ }
776
+ .modal-footer {
777
+ padding: 5px;
778
+ }
779
+ .modal-footer .btn {
780
+ height: 30px;
781
+ padding: 0 10px;
782
+ }
783
+ .modal-footer .form-control {
784
+ width: 30px;
785
+ height: 30px;
786
+ font-size: 12px;
787
+ font-weight: bold;
788
+ }
789
+ .modal-footer .btn-outline-secondary {
790
+ width: 25px;
791
+ height: 25px;
792
+ font-size: 12px;
793
+ line-height: 25px;
794
+ }
795
+ .modal-footer .btn-primary {
796
+ font-size: 12px;
797
+ height: 30px;
798
+ padding: 0 15px;
799
+ border-radius: 5px;
800
+ }
801
+ .btn-primary {
802
+ font-size: 10px;
803
+ width: 50px;
804
+ height: 25px;
805
+ }
806
+ .customisable-text {
807
+ font-size: 8px;
808
+ }
809
+ .button-container {
810
+ gap: 3px;
811
+ }
812
+ .quantity-selector .btn {
813
+ width: 18px;
814
+ height: 18px;
815
+ font-size: 9px;
816
+ line-height: 18px;
817
+ }
818
+ .quantity-selector .quantity-display {
819
+ width: 18px;
820
+ font-size: 9px;
821
+ line-height: 18px;
822
+ }
823
+ .quantity-selector .quantity-to-add,
824
+ .quantity-selector .quantity-to-remove {
825
+ width: 35px;
826
+ height: 18px;
827
+ font-size: 9px;
828
+ }
829
+ .quantity-selector select {
830
+ width: 50px;
831
+ height: 30px;
832
+ font-size: 12px;
833
+ }
834
+ .bottom-action-bar {
835
+ padding: 8px 10px;
836
+ }
837
+ .bottom-action-bar .btn {
838
+ padding: 8px 10px;
839
+ font-size: 14px;
840
+ }
841
+ .cart-icon-badge {
842
+ width: 18px;
843
+ height: 18px;
844
+ font-size: 10px;
845
+ margin-left: 5px;
846
+ }
847
+ .item-details {
848
+ padding: 10px;
849
+ margin: 5px 10px;
850
+ }
851
+ .item-details h6 {
852
+ font-size: 0.95rem;
853
+ }
854
+ .item-details p {
855
+ font-size: 0.8rem;
856
+ }
857
+ .toggle-details {
858
+ font-size: 0.8rem;
859
+ }
860
+ .mic-popup {
861
+ padding: 20px;
862
+ width: 280px;
863
+ }
864
+ .mic-popup-icon {
865
+ font-size: 36px;
866
+ margin-bottom: 15px;
867
  }
868
+ .mic-popup-text {
869
+ font-size: 16px;
870
  }
871
+ .mic-popup-cancel {
872
+ padding: 6px 16px;
873
  font-size: 14px;
874
  }
875
  }
 
946
  <h3>{{ section }}</h3>
947
  <div class="row">
948
  {% for item in items %}
949
+ <div class="col-md-6 mb-4">
950
  <div class="card menu-card">
951
  <video
952
  class="card-img-top menu-video"
 
1001
  <h6>Description</h6>
1002
  <p>{{ item.Description__c | default('No description available') }}</p>
1003
  <h6>Ingredients Info</h6>
1004
+ <p>{{ item.Ingredientsinfo__c | default('Not specified') }}</p>
1005
  <h6>Nutritional Info</h6>
1006
  <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
1007
  <h6>Allergens</h6>
 
1123
  <button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
1124
  </div>
1125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1126
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
1127
  <script>
1128
  let isProcessingRequest = false;
1129
  let currentSoftDrinkButton = null;
1130
 
1131
+ const menuItems = [
1132
  {% for section, items in ordered_menu.items() %}
1133
  {% for item in items %}
1134
+ "{{ item.Name | default('Unnamed Item') | e }}",
 
 
 
 
 
 
 
1135
  {% endfor %}
1136
  {% endfor %}
1137
  ];
 
1152
  "Whole Wheat Flour", "Yogurt (Curd)"
1153
  ];
1154
 
1155
+ // Utility function to sanitize input to prevent XSS
1156
  function sanitizeInput(input) {
1157
  const div = document.createElement('div');
1158
  div.textContent = input;
 
1296
  }
1297
  }
1298
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1299
  document.addEventListener('DOMContentLoaded', function () {
 
 
1300
  const avatarContainer = document.querySelector('.avatar-dropdown-container');
1301
  const dropdownMenu = document.querySelector('.dropdown-menu');
1302
  const avatarIcon = document.getElementById('avatarIcon');
 
1304
  const deleteAvatar = document.getElementById('deleteAvatar');
1305
  const viewAvatar = document.getElementById('viewAvatar');
1306
 
1307
+ // Load avatar from localStorage if available
1308
  function loadAvatar() {
1309
  const savedAvatar = localStorage.getItem('userAvatar');
1310
  const avatarImage = avatarIcon.querySelector('.avatar-image');
 
1323
  }
1324
  }
1325
 
1326
+ // Ensure avatar options (Delete and View) are present only once
1327
  function ensureAvatarOptions() {
1328
  if (!document.querySelector('#deleteAvatar')) {
1329
  const deleteItem = document.createElement('div');
 
1344
  }
1345
  }
1346
 
1347
+ // Remove avatar options
1348
  function removeAvatarOptions() {
1349
  const deleteItem = document.getElementById('deleteAvatar');
1350
  const viewItem = document.getElementById('viewAvatar');
 
1352
  if (viewItem) viewItem.remove();
1353
  }
1354
 
1355
+ // Save avatar to localStorage
1356
  function saveAvatar(imageUrl) {
1357
  localStorage.setItem('userAvatar', imageUrl);
1358
  }
1359
 
1360
+ // Clear avatar from localStorage
1361
  function clearAvatar() {
1362
  localStorage.removeItem('userAvatar');
1363
  avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
1364
  removeAvatarOptions();
1365
  }
1366
 
1367
+ // Avatar click handler
1368
  avatarIcon.addEventListener('click', function(event) {
1369
  event.stopPropagation();
1370
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1371
  });
1372
 
1373
+ // Handle image upload with 15MB limit and error handling
1374
  avatarUpload.addEventListener('change', function(event) {
1375
  const file = event.target.files[0];
1376
  if (file) {
1377
+ const maxSize = 15 * 1024 * 1024; // 15MB in bytes
1378
  if (file.size > maxSize) {
1379
  alert('Image size must not exceed 15MB.');
1380
  this.value = '';
 
1407
  ensureAvatarOptions();
1408
  dropdownMenu.style.display = 'none';
1409
  } else {
1410
+ alert('Failed to upload image: ' + (data.error || 'Unknown error. Using default image.'));
1411
+ const img = document.createElement('img');
1412
+ img.src = '/static/default-avatar.jpg';
1413
+ img.alt = 'User Avatar';
1414
+ img.className = 'avatar-image';
1415
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1416
+ avatarIcon.innerHTML = '';
1417
+ avatarIcon.appendChild(img);
1418
+ saveAvatar('/static/default-avatar.jpg');
1419
+ ensureAvatarOptions();
1420
+ dropdownMenu.style.display = 'none';
1421
  }
1422
  })
1423
  .catch(error => {
1424
  console.error('Upload error:', error);
1425
+ alert('Error uploading image. Using default image as fallback.');
1426
+ const img = document.createElement('img');
1427
+ img.src = '/static/default-avatar.jpg';
1428
+ img.alt = 'User Avatar';
1429
+ img.className = 'avatar-image';
1430
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1431
+ avatarIcon.innerHTML = '';
1432
+ avatarIcon.appendChild(img);
1433
+ saveAvatar('/static/default-avatar.jpg');
1434
+ ensureAvatarOptions();
1435
+ dropdownMenu.style.display = 'none';
1436
  });
1437
  };
1438
+ reader.onerror = function() {
1439
+ alert('Error reading the image file. Using default image as fallback.');
1440
+ const img = document.createElement('img');
1441
+ img.src = '/static/default-avatar.jpg';
1442
+ img.alt = 'User Avatar';
1443
+ img.className = 'avatar-image';
1444
+ img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
1445
+ avatarIcon.innerHTML = '';
1446
+ avatarIcon.appendChild(img);
1447
+ saveAvatar('/static/default-avatar.jpg');
1448
+ ensureAvatarOptions();
1449
+ dropdownMenu.style.display = 'none';
1450
+ };
1451
  reader.readAsDataURL(file);
1452
  }
1453
  });
1454
 
1455
+ // Handle image deletion
1456
  function addDeleteListener(deleteElement) {
1457
  deleteElement.addEventListener('click', function() {
1458
  fetch('/delete_avatar', {
 
1461
  'Content-Type': 'application/json'
1462
  }
1463
  })
1464
+ .then(response => {
1465
+ if (!response.ok) throw new Error('Delete failed');
1466
+ return response.json();
1467
+ })
1468
  .then(data => {
1469
  if (data.success) {
1470
  clearAvatar();
 
1475
  })
1476
  .catch(error => {
1477
  console.error('Delete error:', error);
1478
+ alert('Error deleting image. Please try again.');
1479
  });
1480
  });
1481
  }
1482
 
1483
+ // Handle avatar view
1484
  function addViewListener(viewElement) {
1485
  viewElement.addEventListener('click', function() {
1486
  const avatarImage = avatarIcon.querySelector('.avatar-image');
 
1489
  modalImg.src = avatarImage.src;
1490
  const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
1491
  modal.show();
1492
+ } else {
1493
+ alert('No avatar image to view.');
1494
  }
1495
  dropdownMenu.style.display = 'none';
1496
  });
1497
  }
1498
 
1499
+ // Add listeners if elements exist
1500
  if (deleteAvatar) addDeleteListener(deleteAvatar);
1501
  if (viewAvatar) addViewListener(viewAvatar);
1502
 
1503
+ // Load avatar on page load
1504
  loadAvatar();
1505
 
1506
+ // Close dropdown when clicking outside
1507
  document.addEventListener('click', function(event) {
1508
  if (!avatarContainer.contains(event.target)) {
1509
  dropdownMenu.style.display = 'none';
 
1512
 
1513
  const menuCards = document.querySelectorAll('.menu-card');
1514
  const menuVideos = document.querySelectorAll('.menu-video');
1515
+ const cardObserver = new IntersectionObserver((entries, observer) => {
1516
  entries.forEach(entry => {
1517
  if (entry.isIntersecting) {
1518
  entry.target.classList.add('visible');
1519
+ observer.unobserve(entry.target);
1520
  }
1521
  });
1522
+ }, {
1523
+ root: null,
1524
+ rootMargin: '0px',
1525
+ threshold: 0.1
1526
+ });
1527
+ const videoObserver = new IntersectionObserver((entries, observer) => {
1528
  entries.forEach(entry => {
1529
  if (entry.isIntersecting) {
1530
  const video = entry.target;
1531
  const src = video.getAttribute('data-src');
1532
+ if (src && !video.querySelector('source[src="' + src + '"]')) {
1533
  const source = video.querySelector('source');
1534
  source.src = src;
1535
  video.load();
1536
  }
1537
  video.classList.add('loaded');
1538
+ observer.unobserve(video);
1539
  }
1540
  });
1541
+ }, {
1542
+ root: null,
1543
+ rootMargin: '200px',
1544
+ threshold: 0.01
1545
+ });
1546
  menuCards.forEach(card => cardObserver.observe(card));
1547
  menuVideos.forEach(video => videoObserver.observe(video));
1548
 
 
1551
  link.addEventListener('click', function () {
1552
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1553
  const detailsDiv = document.getElementById(`details-${itemName}`);
1554
+ const isCurrentlyShown = detailsDiv.classList.contains('show');
1555
+
1556
+ document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1557
+ if (otherDetails !== detailsDiv) {
1558
+ otherDetails.classList.remove('show');
1559
+ const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
1560
+ if (otherLink) {
1561
+ otherLink.innerText = 'Show Details';
1562
+ }
1563
  }
1564
  });
1565
+
1566
+ if (!isCurrentlyShown) {
1567
+ detailsDiv.classList.add('show');
1568
+ this.innerText = 'Hide Details';
1569
+ } else {
1570
+ detailsDiv.classList.remove('show');
1571
+ this.innerText = 'Show Details';
1572
+ }
1573
  });
1574
  });
1575
 
 
1591
 
1592
  const searchBar = document.getElementById('searchBar');
1593
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1594
+ const debouncedFilterMenu = debounce(filterMenu, 300);
 
1595
  searchBar.addEventListener('input', function () {
1596
  const input = sanitizeInput(this.value.trim().toLowerCase());
1597
  suggestionsContainer.innerHTML = '';
1598
  suggestionsContainer.style.display = 'none';
1599
+ if (input) {
1600
+ const filteredItems = menuItems.filter(item =>
1601
+ item.toLowerCase().includes(input)
 
 
 
1602
  );
 
1603
  if (filteredItems.length > 0) {
1604
+ filteredItems.forEach(item => {
1605
  const suggestionDiv = document.createElement('div');
1606
  suggestionDiv.classList.add('suggestion-item');
1607
+ suggestionDiv.innerText = item;
 
 
 
1608
  suggestionDiv.addEventListener('click', function () {
1609
+ searchBar.value = item;
1610
  suggestionsContainer.style.display = 'none';
1611
+ debouncedFilterMenu();
1612
  });
1613
  suggestionsContainer.appendChild(suggestionDiv);
1614
  });
1615
  suggestionsContainer.style.display = 'block';
1616
  }
1617
  }
1618
+ debouncedFilterMenu();
1619
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
1620
  document.addEventListener('click', function (event) {
1621
+ if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
 
 
1622
  suggestionsContainer.style.display = 'none';
 
1623
  }
1624
  });
1625
 
 
1657
  suggestionDiv.innerText = ingredient;
1658
  suggestionDiv.addEventListener('click', function () {
1659
  const currentValue = descriptionTextarea.value;
1660
+ const lastCommaIndexIt = currentValue.lastIndexOf(',');
1661
+ const baseText = lastCommaIndexIt !== -1 ? currentValue.substring(0, lastCommaIndexIt + 1) : '';
1662
  descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
1663
  descriptionSuggestions.style.display = 'none';
1664
  descriptionTextarea.focus();
 
1677
  });
1678
  }
1679
 
1680
+ // Custom Dish Form Validation
1681
  const customDishForm = document.getElementById('customDishForm');
1682
  if (customDishForm) {
1683
  customDishForm.addEventListener('submit', function(event) {
 
1684
  const dishName = document.getElementById('custom-dish-name').value.trim();
1685
  const description = document.getElementById('custom-dish-description').value.trim();
1686
  if (!dishName || !description) {
1687
+ event.preventDefault();
1688
+ alert('Please fill in both the dish name and description.');
1689
  return;
1690
  }
1691
+ event.preventDefault();
1692
  fetch('/customdish/generate_custom_dish', {
1693
  method: 'POST',
1694
  headers: {
 
1705
  alert('Custom dish submitted successfully!');
1706
  window.location.reload();
1707
  } else {
1708
+ alert('Failed to submit custom dish: ' + (data.error || 'Unknown error'));
1709
  }
1710
  })
1711
  .catch(error => {
1712
+ console.error('Error submitting custom dish:', error);
1713
+ alert('Error submitting custom dish. Please try again.');
1714
  });
1715
  });
1716
  }
1717
 
1718
  fetch('/cart/get')
1719
+ .then(response => {
1720
+ if (!response.ok) {
1721
+ throw new Error(`HTTP error! Status: ${response.status}`);
1722
+ }
1723
+ return response.json();
1724
+ })
1725
  .then(data => {
1726
  if (data.success) {
1727
  updateCartUI(data.cart);
1728
  } else {
1729
+ console.error('Failed to fetch cart:', data.error);
1730
+ const cart = getCartLocalStorage();
1731
+ updateCartUI(cart);
1732
  }
1733
  })
1734
  .catch(err => {
1735
  console.error('Error fetching cart:', err);
1736
+ const cart = getCartLocalStorage();
1737
+ updateCartUI(cart);
1738
  });
1739
 
1740
+ const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
1741
+ preloadedVideos.forEach(link => {
1742
+ const video = document.createElement('video');
1743
+ video.src = link.href;
1744
+ video.preload = 'auto';
1745
+ });
1746
+
1747
  const decreaseBtn = document.getElementById('decreaseQuantity');
1748
  const increaseBtn = document.getElementById('increaseQuantity');
1749
  const quantityInput = document.getElementById('quantityInput');
1750
  decreaseBtn.addEventListener('click', function () {
1751
+ let currentQuantity = parseInt(quantityInput.value);
1752
+ if (currentQuantity > 1) {
1753
+ currentQuantity--;
1754
+ quantityInput.value = currentQuantity;
1755
+ }
1756
  });
1757
  increaseBtn.addEventListener('click', function () {
1758
+ let currentQuantity = parseInt(quantityInput.value);
1759
+ currentQuantity++;
1760
+ quantityInput.value = currentQuantity;
1761
  });
1762
 
1763
  const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
1764
  const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
1765
  const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
1766
+
1767
  softDrinkDecreaseBtn.addEventListener('click', function() {
1768
+ let currentQuantity = parseInt(softDrinkQuantityInput.value);
1769
+ if (currentQuantity > 1) {
1770
+ currentQuantity--;
1771
+ softDrinkQuantityInput.value = currentQuantity;
1772
+ }
1773
  });
1774
+
1775
  softDrinkIncreaseBtn.addEventListener('click', function() {
1776
+ let currentQuantity = parseInt(softDrinkQuantityInput.value);
1777
+ if (currentQuantity < 1000) {
1778
+ currentQuantity++;
1779
+ softDrinkQuantityInput.value = currentQuantity;
1780
+ }
1781
  });
1782
 
1783
+ // Mic Popup Functionality
1784
  const micIcon = document.getElementById('micIcon');
1785
  const micUnsupported = document.getElementById('micUnsupported');
1786
  const micPopup = document.getElementById('micPopup');
 
1803
  recognition.onresult = (event) => {
1804
  let interimTranscript = '';
1805
  let finalTranscript = '';
1806
+
1807
  for (let i = event.resultIndex; i < event.results.length; i++) {
1808
  const transcript = event.results[i][0].transcript;
1809
+ if (event.results[i].isFinal) {
1810
+ finalTranscript += transcript;
1811
+ } else {
1812
+ interimTranscript += transcript;
1813
+ }
1814
+ }
1815
+
1816
+ if (interimTranscript) {
1817
+ micPopupText.textContent = interimTranscript;
1818
  }
1819
+
1820
  if (finalTranscript) {
1821
  searchBar.value = sanitizeInput(finalTranscript.trim());
1822
+ debouncedFilterMenu();
1823
  micPopup.classList.remove('active');
1824
  }
1825
  };
1826
 
1827
  recognition.onend = () => {
1828
  micIcon.classList.remove('active');
1829
+ if (micPopup.classList.contains('active')) {
1830
+ setTimeout(() => {
1831
+ micPopup.classList.remove('active');
1832
+ }, 1000);
1833
+ }
1834
  };
1835
 
1836
  recognition.onerror = (event) => {
1837
  micIcon.classList.remove('active');
1838
  micPopupText.textContent = 'Error: ' + event.error;
1839
+ setTimeout(() => {
1840
+ micPopup.classList.remove('active');
1841
+ }, 2000);
1842
  console.error('Speech error:', event.error);
1843
  };
1844
 
 
1847
  recognition.start();
1848
  } catch (e) {
1849
  micPopupText.textContent = 'Error starting microphone';
1850
+ setTimeout(() => {
1851
+ micPopup.classList.remove('active');
1852
+ }, 2000);
1853
+ console.error('Recognition start error:', e);
1854
  }
1855
  });
1856
 
 
1865
  }
1866
  });
1867
 
1868
+ function filterMenu() {
1869
+ const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
 
 
 
1870
  const sections = document.querySelectorAll('h3');
1871
  const items = document.querySelectorAll('.menu-card');
1872
  let matchedSections = new Set();
 
1873
  items.forEach(item => {
1874
  const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1875
  const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1876
+ if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
 
 
 
 
1877
  item.style.display = 'block';
1878
  item.classList.add('visible');
1879
  matchedSections.add(item.closest('.row'));
 
1881
  item.style.display = 'none';
1882
  }
1883
  });
 
1884
  sections.forEach(section => {
1885
  const sectionRow = section.nextElementSibling;
1886
  if (matchedSections.has(sectionRow)) {
 
1891
  sectionRow.style.display = 'none';
1892
  }
1893
  });
 
1894
  if (!input) {
1895
  sections.forEach(section => {
1896
  section.style.display = 'block';
 
1902
  });
1903
  }
1904
  }
1905
+
1906
+ function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
1907
  document.getElementById('modal-name').innerText = name;
1908
  document.getElementById('modal-price').innerText = `$${price}`;
1909
+ const modalImg = document.getElementById('modal-img');
1910
+ modalImg.src = image || '/static/placeholder.jpg';
1911
  document.getElementById('modal-description').innerText = description || 'No description available.';
1912
  document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
1913
  document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
 
1957
  document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
1958
  });
1959
  }
1960
+ document.addEventListener('click', function(event) {
 
1961
  if (event.target.classList.contains('addon-option')) {
1962
  handleAddonClick(event.target);
1963
  }