lokesh341's picture
Update templates/menu.html
e30adb9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menu</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<!-- Preload Critical Resources -->
<link rel="preload" href="/static/placeholder.mp4" as="video">
{% for section, items in ordered_menu.items() %}
{% for item in items[:1] %}
<link rel="preload" href="{{ item.Video1__c | default('/static/placeholder.mp4') }}" as="video" fetchpriority="high">
{% endfor %}
{% endfor %}
<style>
body {
font-family: Arial, sans-serif;
background-color: #fdf4e3;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
padding-bottom: 70px;
}
.container {
max-width: 900px;
padding: 0 15px;
}
.menu-card {
max-width: 350px;
border-radius: 15px;
overflow: hidden;
background-color: #fff;
margin: auto;
display: flex;
flex-direction: column;
opacity: 0;
transition: opacity 0.3s ease-in-out, transform 0.3s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.menu-card.visible {
opacity: 1;
transform: translateY(0);
}
.menu-video {
height: 200px;
width: 100%;
object-fit: cover;
border-radius: 15px 15px 0 0;
opacity: 0;
transition: opacity 0.5s ease-in-out;
background-color: #000;
}
.menu-video.loaded {
opacity: 1;
}
.menu-card:hover .menu-video {
opacity: 1;
transform: scale(1.05);
}
.menu-card .card-body .card-title {
font-size: 1.2rem;
font-weight: 600;
margin: 10px 0;
color: #333333;
}
.menu-card .card-body .card-text.price {
font-size: 1rem;
font-weight: 500;
color: #000000;
margin-bottom: 5px;
}
.addbutton .btn {
background-color: #28a745;
color: white;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
border-radius: 5px;
border: none;
transition: background-color 0.3s ease, transform 0.2s ease;
margin-left: 13px;
}
.addbutton .btn:hover {
background-color: #218838;
transform: scale(1.05);
}
.button-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
}
.customisable-text {
color: #0FAA39;
font-size: 10px;
font-weight: 500;
margin: 0;
text-align: center;
line-height: 1;
}
.btn-primary {
font-size: 12px;
font-weight: bold;
border-radius: 8px;
width: 70px;
height: 35px;
background-color: #0FAA39;
border-color: #0FAA39;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background-color 0.3s ease, transform 0.1s ease;
}
.btn-primary:hover {
background-color: #0D9232;
border-color: #0D9232;
transform: scale(1.05);
}
.btn-primary:active,
.btn-primary:focus {
background-color: #0B7A29;
border-color: #0B7A29;
box-shadow: none;
transform: scale(0.98);
}
.avatar-dropdown-container {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #007bff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
font-weight: bold;
position: relative;
transition: transform 0.2s ease;
}
.avatar-icon:hover {
transform: scale(1.1);
}
.avatar-icon img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
transition: transform 0.2s ease;
}
.avatar-icon img:hover {
transform: scale(1.1);
}
.dropdown-menu {
position: absolute;
right: 0;
top: 100%;
background-color: #fff8f0;
border-radius: 8px;
width: 200px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: none;
border: 1px solid #ffd8b1;
padding: 5px 0;
z-index: 1000;
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.dropdown-menu .dropdown-item {
padding: 10px 16px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: background-color 0.2s ease, color 0.2s ease;
cursor: pointer;
}
.dropdown-menu .dropdown-item:hover {
background-color: #ffe4c4;
color: #2c2c2c;
}
.upload-item,
.delete-item,
.view-item {
padding: 10px 16px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: background-color 0.2s ease;
cursor: pointer;
}
.upload-item:hover,
.delete-item:hover,
.view-item:hover {
background-color: #ffe4c4;
color: #2c2c2c;
}
.fixed-top-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 54px;
background: linear-gradient(45deg, #FFA07A, #FFB347);
color: white;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.search-bar-container {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
width: 300px;
max-width: 90%;
position: relative;
}
.search-bar-container input {
width: 100%;
padding: 8px 40px 8px 40px;
font-size: 16px;
border-radius: 25px;
border: none;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
outline: none;
transition: box-shadow 0.3s ease;
}
.search-bar-container input:focus {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.search-bar-container input::placeholder {
color: #888;
}
.search-icon {
position: absolute;
left: 15px;
font-size: 18px;
color: #888;
}
.mic-icon {
position: absolute;
right: 15px;
font-size: 18px;
color: #888;
cursor: pointer;
transition: color 0.3s ease;
}
.mic-icon.active {
color: #007bff;
}
.mic-unsupported {
display: none;
position: absolute;
right: 15px;
font-size: 14px;
color: #888;
background-color: #fff;
padding: 2px 8px;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.autocomplete-suggestions {
position: absolute;
top: 100%;
left: 0;
width: 100%;
max-height: 200px;
overflow-y: auto;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
display: none;
}
.autocomplete-suggestions .suggestion-item {
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.2s ease;
}
.autocomplete-suggestions .suggestion-item:hover {
background-color: #f1f1f1;
}
.addon-section {
background-color: #fff;
border: 2px solid #ffa500;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
}
.addon-section h6 {
margin-bottom: 10px;
font-size: 1.1rem;
font-weight: bold;
color: #343a40;
}
.addon-section .form-check {
display: inline-flex;
align-items: center;
margin-left: 10px;
color: #343a40;
}
.addon-section .form-check-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #343a40;
border-radius: 5px;
background-color: #f0f0f0;
position: relative;
margin-right: 10px;
}
.addon-section .form-check-input:checked {
background-color: #006400;
border-color: #006400;
}
.addon-section .form-check-input:checked::before {
content: '\2713';
font-size: 14px;
position: absolute;
top: 3px;
left: 4px;
color: white;
}
.addon-section .form-check-label {
font-size: 16px;
margin-left: 5px;
margin-right: 15px;
cursor: pointer;
display: inline-block;
vertical-align: middle;
}
form.text-center.mb-4 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 5px;
}
.modal-header {
padding: 10px 15px;
}
.modal-title {
font-size: 16px;
font-weight: bold;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
padding: 15px;
text-align: center;
}
.modal-body #avatar-modal-img {
max-width: 100%;
max-height: 400px;
object-fit: contain;
border-radius: 8px;
margin: 0 auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.modal-body #modal-img {
max-height: 200px;
width: 100%;
object-fit: cover;
border-radius: 8px;
margin-bottom: 10px;
}
.modal-body #modal-name {
font-size: 20px;
font-weight: bold;
text-align: center;
margin-bottom: 5px;
color: #333333;
}
.modal-body #modal-price {
font-size: 16px;
font-weight: 500;
color: #000000;
text-align: center;
margin-bottom: 10px;
}
.modal-body #modal-description {
font-size: 14px;
color: #6c757d;
margin-bottom: 10px;
}
.modal-body .nutritional-info {
font-size: 12px;
color: #6c757d;
margin-bottom: 10px;
}
.modal-body #modal-addons h6,
.modal-body #first-row h6 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
}
.modal-footer .d-flex {
display: flex;
align-items: center;
gap: 10px;
}
.modal-footer .btn {
height: 40px;
padding: 0 15px;
}
.modal-footer .form-control {
width: 50px;
height: 40px;
text-align: center;
}
.modal-footer .btn-primary {
background-color: #0FAA39;
border-color: #0FAA39;
font-weight: bold;
padding: 10px 20px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
width: auto;
}
.modal-footer .btn-outline-secondary {
height: 40px;
width: 40px;
}
.item-details {
display: none;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
margin-top: 10px;
margin: 10px 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.item-details.show {
display: block;
}
.item-details h6 {
color: #0FAA39;
margin-bottom: 10px;
font-size: 1.1rem;
font-weight: bold;
}
.item-details p {
margin-bottom: 15px;
color: #555;
line-height: 1.5;
font-size: 0.9rem;
}
.toggle-details {
cursor: pointer;
color: #0FAA39;
font-size: 0.9rem;
text-align: left;
padding: 5px 0;
transition: color 0.3s ease;
display: block;
width: 100%;
margin-top: 5px;
}
.toggle-details:hover {
color: #0D9232;
text-decoration: underline;
}
.category-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 10px;
}
.category-button {
background-color: #fff;
border: 2px solid #0FAA39;
color: #0FAA39;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.category-button.selected {
background-color: #0FAA39;
color: #fff;
border-color: #0FAA39;
}
.category-button:hover {
background-color: #e6f4ea;
}
.quantity-selector {
display: flex;
align-items: center;
gap: 5px;
}
.quantity-selector .btn {
width: 25px;
height: 25px;
padding: 0;
font-size: 12px;
line-height: 25px;
text-align: center;
}
.quantity-selector .quantity-display {
width: 25px;
text-align: center;
font-size: 12px;
font-weight: bold;
line-height: 25px;
}
.quantity-selector .quantity-to-add,
.quantity-selector .quantity-to-remove {
width: 45px;
height: 25px;
font-size: 12px;
padding: 0 5px;
}
.modal-dialog {
max-height: 90vh;
}
.modal-body::-webkit-scrollbar {
width: 8px;
}
.modal-body::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb {
background: #0FAA39;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb:hover {
background: #0D9232;
}
.btn-primary:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.quantity-selector select {
width: 60px;
height: 35px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ced4da;
}
#custom-dish-form {
position: relative;
padding-bottom: 80px;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
max-height: 70vh;
overflow-y: auto;
}
#custom-dish-form h3 {
font-size: 1.3rem;
color: #0FAA39;
margin-bottom: 20px;
text-align: center;
}
#custom-dish-form .btn-primary {
position: relative;
right: auto;
bottom: auto;
width: 100%;
padding: 10px 20px;
margin-top: 15px;
}
#custom-dish-form::-webkit-scrollbar {
width: 8px;
}
#custom-dish-form::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
#custom-dish-form::-webkit-scrollbar-thumb {
background: #0FAA39;
border-radius: 10px;
}
#custom-dish-form::-webkit-scrollbar-thumb:hover {
background: #0D9232;
}
#dishNameSuggestions {
position: absolute;
width: calc(100% - 30px);
max-height: 200px;
overflow-y: auto;
background: white;
border: 1px solid #ddd;
border-radius: 0 0 4px 4px;
z-index: 1000;
display: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.bottom-action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
max-width: 900px;
margin: 0 auto;
}
.bottom-action-bar .btn {
flex: 1;
margin: 0 5px;
padding: 10px 15px;
border-radius: 8px;
font-weight: bold;
font-size: 16px;
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
min-width: 0;
white-space: nowrap;
transition: transform 0.2s ease;
}
.bottom-action-bar .btn:hover {
transform: scale(1.05);
}
.bottom-action-bar .btn-order-history {
background-color: #FFA07A;
border-color: #FFA07A;
}
.bottom-action-bar .btn-order-history:hover {
background-color: #FF8C61;
border-color: #FF8C61;
}
.bottom-action-bar .btn-view-cart {
background-color: #0FAA39;
border-color: #0FAA39;
}
.bottom-action-bar .btn-view-cart:hover {
background-color: #0D9232;
border-color: #0D9232;
}
.cart-icon-badge {
background-color: white;
color: #0FAA39;
border-radius: 50%;
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-left: 8px;
transition: all 0.3s ease;
}
.mic-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
z-index: 2000;
display: none;
width: 300px;
max-width: 90%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.mic-popup.active {
display: block;
}
.mic-popup-icon {
font-size: 48px;
margin-bottom: 20px;
color: #ff4444;
animation: pulse 1.5s infinite;
}
.mic-popup-text {
font-size: 18px;
margin-bottom: 15px;
min-height: 24px;
}
.mic-popup-cancel {
background-color: #ff4444;
color: white;
border: none;
padding: 8px 20px;
border-radius: 20px;
cursor: pointer;
font-weight: bold;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@media (max-width: 576px) {
.fixed-top-bar {
height: 60px;
padding: 10px;
}
.search-bar-container {
width: 80%;
max-width: 100%;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
.search-bar-container input {
padding: 6px 35px 6px 35px;
font-size: 14px;
border-radius: 20px;
}
.search-icon {
left: 12px;
font-size: 16px;
}
.mic-icon {
right: 12px;
font-size: 16px;
}
.avatar-dropdown-container {
right: 10px;
}
.avatar-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.dropdown-menu {
width: 180px;
}
.dropdown-menu .dropdown-item {
padding: 8px 12px;
font-size: 13px;
}
.upload-item,
.delete-item,
.view-item {
padding: 8px 12px;
font-size: 13px;
}
.category-buttons {
gap: 8px;
}
.category-button {
padding: 4px 12px;
font-size: 0.85rem;
}
.modal-dialog {
max-width: 96%;
margin: 5px auto;
}
.modal-header {
padding: 5px 10px;
}
.modal-title {
font-size: 14px;
}
.modal-body {
max-height: 50vh;
padding: 8px;
}
.modal-body #avatar-modal-img {
max-height: 300px;
}
.modal-body #modal-img {
max-height: 150px;
width: 100%;
max-width: 150px;
margin: 0 auto 5px;
display: block;
}
.modal-body #modal-name {
font-size: 18px;
margin-bottom: 3px;
}
.modal-body #modal-price {
font-size: 14px;
margin-bottom: 5px;
}
.modal-body #modal-description {
font-size: 12px;
margin-bottom: 5px;
}
.modal-body .nutritional-info {
font-size: 10px;
margin-bottom: 5px;
}
.modal-body #modal-addons h6,
.modal-body #first-row h6 {
font-size: 12px;
margin-bottom: 5px;
}
.modal-footer {
padding: 5px;
}
.modal-footer .btn {
height: 30px;
padding: 0 10px;
}
.modal-footer .form-control {
width: 30px;
height: 30px;
font-size: 12px;
font-weight: bold;
}
.modal-footer .btn-outline-secondary {
width: 25px;
height: 25px;
font-size: 12px;
line-height: 25px;
}
.modal-footer .btn-primary {
font-size: 12px;
height: 30px;
padding: 0 15px;
border-radius: 5px;
}
.btn-primary {
font-size: 10px;
width: 50px;
height: 25px;
}
.customisable-text {
font-size: 8px;
}
.button-container {
gap: 3px;
}
.quantity-selector .btn {
width: 18px;
height: 18px;
font-size: 9px;
line-height: 18px;
}
.quantity-selector .quantity-display {
width: 18px;
font-size: 9px;
line-height: 18px;
}
.quantity-selector .quantity-to-add,
.quantity-selector .quantity-to-remove {
width: 35px;
height: 18px;
font-size: 9px;
}
.quantity-selector select {
width: 50px;
height: 30px;
font-size: 12px;
}
.bottom-action-bar {
padding: 8px 10px;
}
.bottom-action-bar .btn {
padding: 8px 10px;
font-size: 14px;
}
.cart-icon-badge {
width: 18px;
height: 18px;
font-size: 10px;
margin-left: 5px;
}
.item-details {
padding: 10px;
margin: 5px 10px;
}
.item-details h6 {
font-size: 0.95rem;
}
.item-details p {
font-size: 0.8rem;
}
.toggle-details {
font-size: 0.8rem;
}
.mic-popup {
padding: 20px;
width: 280px;
}
.mic-popup-icon {
font-size: 36px;
margin-bottom: 15px;
}
.mic-popup-text {
font-size: 16px;
}
.mic-popup-cancel {
padding: 6px 16px;
font-size: 14px;
}
#custom-dish-form {
padding: 15px;
max-height: 60vh;
}
}
</style>
</head>
<body>
<div class="fixed-top-bar" role="banner">
<div class="avatar-dropdown-container">
<div class="avatar-icon" id="avatarIcon" role="button" aria-label="User Menu">
{% if user_image %}
<img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
{% else %}
<span>{{ first_letter }}</span>
{% endif %}
</div>
<div class="dropdown-menu" id="avatarDropdown" role="menu">
{% if user_image %}
<div class="dropdown-item delete-item" id="deleteAvatar" role="menuitem">Delete Image</div>
<div class="dropdown-item view-item" id="viewAvatar" role="menuitem">View Avatar</div>
{% endif %}
<a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item" role="menuitem">Order History</a>
<a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item" role="menuitem">View Profile</a>
<div class="dropdown-item upload-item" role="menuitem">
<label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
Upload Image
</label>
<input type="file" id="avatarUpload" accept="image/*" style="display: none;" aria-label="Upload Avatar">
</div>
<a href="{{ url_for('logout') }}" class="dropdown-item" role="menuitem">Logout</a>
</div>
</div>
<div class="search-bar-container">
<input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off" aria-label="Search menu items">
<i class="bi bi-search search-icon"></i>
<i class="bi bi-mic mic-icon" id="micIcon" role="button" aria-label="Voice Search"></i>
<span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
<div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
</div>
</div>
<form method="get" action="/menu" class="text-center mb-4" id="categoryForm" role="form">
<label class="form-label fw-bold" for="selectedCategoryInput">Select a Category:</label>
<div class="category-buttons">
{% for category in categories %}
<button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}" aria-pressed="{% if selected_category == category %}true{% else %}false{% endif %}">{{ category }}</button>
{% endfor %}
<button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish" aria-pressed="{% if selected_category == 'Customized Dish' %}true{% else %}false{% endif %}">Customized Dish</button>
</div>
<input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
</form>
<div class="container mt-4" role="main">
{% if selected_category == "Customized Dish" %}
<div id="custom-dish-form" class="mt-4">
<h3>Create Your Custom Dish</h3>
<form id="customDishForm" role="form">
<div class="mb-3 position-relative">
<label for="custom-dish-name" class="form-label">Dish Name</label>
<input type="text" class="form-control" id="custom-dish-name" name="name" required autocomplete="off" placeholder="e.g., Spicy Chicken Curry" aria-label="Custom Dish Name">
<div id="dishNameSuggestions" class="autocomplete-suggestions"></div>
</div>
<div class="mb-3">
<label for="custom-dish-ingredients" class="form-label">Ingredients List</label>
<textarea class="form-control" id="custom-dish-ingredients" name="ingredients" required rows="3" placeholder="e.g., Chicken, Tomatoes, Spices" aria-label="Ingredients List"></textarea>
</div>
<div class="mb-3">
<label for="custom-dish-description" class="form-label">Dish Description</label>
<textarea class="form-control" id="custom-dish-description" name="description" required rows="3" placeholder="e.g., A spicy and flavorful chicken dish" aria-label="Dish Description"></textarea>
</div>
<div class="mb-3">
<label for="custom-dish-price" class="form-label">Price ($)</label>
<input type="number" class="form-control" id="custom-dish-price" name="price" step="0.01" min="0.01" required placeholder="e.g., 12.99" aria-label="Price">
</div>
<button type="submit" class="btn btn-primary" id="submitCustomDish" aria-label="Submit Custom Dish">Submit Custom Dish</button>
<div id="form-feedback" class="mt-3" style="display: none; text-align: center;"></div>
</form>
</div>
{% else %}
{% if ordered_menu.items()|length == 0 %}
<p class="text-center">No menu items available for this category.</p>
{% else %}
{% for section, items in ordered_menu.items() %}
<h3>{{ section }}</h3>
<div class="row">
{% for item in items %}
<div class="col-md-6 mb-4" data-item-name="{{ item.Name | default('Unnamed Item') | e }}">
<div class="card menu-card">
<video
class="card-img-top menu-video"
muted
loop
preload="auto"
data-src="{{ item.Video1__c | default('/static/placeholder.mp4') }}"
poster="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
width="350"
height="200"
onmouseover="this.play()"
onmouseout="this.pause(); this.currentTime = 0;"
onerror="this.poster='/static/placeholder.jpg';"
aria-label="Video preview of {{ item.Name | default('Unnamed Item') }}">
<source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="addbutton">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
<p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
{% if item.Section__c != 'Soft Drinks' %}
<div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}" role="button" aria-expanded="false" aria-controls="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">Show Details</div>
{% endif %}
</div>
<div class="d-flex flex-column align-item-center justify-content-center">
<div class="button-container"
data-item-name="{{ item.Name | default('Unnamed Item') }}"
data-item-price="{{ item.Price__c | default('0.00') }}"
data-item-image="{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}"
data-item-section="{{ item.Section__c | default(section) }}"
data-item-category="{{ selected_category }}">
{% if item.Section__c == 'Soft Drinks' %}
<button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)" aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">ADD</button>
{% else %}
<button class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#itemModal"
onclick="showItemDetails('{{ item.Name | default('Unnamed Item') | e }}', '{{ item.Price__c | default('0.00') }}', '{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}', '{{ item.Description__c | default('No description') | e }}', '{{ item.IngredientsInfo__c | default('Not specified') | e }}', '{{ item.NutritionalInfo__c | default('Not available') | e }}', '{{ item.Allergens__c | default('None listed') | e }}', '{{ item.Section__c | default(section) | e }}', '{{ selected_category | e }}')"
aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">
ADD
</button>
{% endif %}
{% if item.Section__c != 'Apetizer' and item.Section__c != 'Customized dish' and item.Section__c != 'Soft Drinks' %}
<span class="customisable-text">Customisable</span>
{% endif %}
</div>
</div>
</div>
</div>
{% if item.Section__c != 'Soft Drinks' %}
<div class="item-details" id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">
<h6>Description</h6>
<p>{{ item.Description__c | default('No description available') }}</p>
<h6>Ingredients Info</h6>
<p>{{ item.Ingredientsinfo__c | default('Not specified') }}</p>
<h6>Nutritional Info</h6>
<p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
<h6>Allergens</h6>
<p>{{ item.Allergens__c | default('None listed') }}</p>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% endif %}
{% endif %}
</div>
<div class="bottom-action-bar" role="navigation">
<a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history" aria-label="View Order History">
<i class="bi bi-clock-history"></i> Order History
</a>
<a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart" aria-label="View Cart">
<i class="bi bi-cart"></i> View Cart
<span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
</a>
</div>
<!-- Modal for Item Details -->
<div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="itemModalLabel">Item Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="modal-img" class="img-fluid rounded mb-3 d-block mx-auto" alt="Item Image" style="max-height: 200px; object-fit: cover;">
<h5 id="modal-name" class="fw-bold text-center"></h5>
<p id="modal-price" class="text-muted text-center"></p>
<p id="modal-description" class="text-secondary"></p>
<div class="nutritional-info">
<strong>Ingredients:</strong> <span id="modal-ingredients"></span><br>
<strong>Nutrition:</strong> <span id="modal-nutrition"></span><br>
<strong>Allergens:</strong> <span id="modal-allergens"></span>
</div>
<div id="modal-addons" class="modal-addons mt-4">
<h6>Customization Options</h6>
<div id="addons-list" class="addons-container">Loading customization options...</div>
</div>
<div class="mt-4">
<h6>Custom Request</h6>
<textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..." aria-label="Special Instructions"></textarea>
</div>
<span id="modal-section" data-section="" data-category="" style="display: none;"></span>
</div>
<div class="modal-footer d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-secondary" id="decreaseQuantity" aria-label="Decrease Quantity">-</button>
<input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;" aria-label="Quantity"/>
<button type="button" class="btn btn-outline-secondary" id="increaseQuantity" aria-label="Increase Quantity">+</button>
</div>
<button type="button" class="btn btn-primary" onclick="addToCartFromModal()" aria-label="Add to Cart">Add to Cart</button>
</div>
</div>
</div>
</div>
<!-- Modal for Avatar View -->
<div class="modal fade" id="avatarModal" tabindex="-1" aria-labelledby="avatarModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="avatarModalLabel">View Avatar</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Close Avatar Modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal for Soft Drinks Quantity Selection -->
<div class="modal fade" id="softDrinkModal" tabindex="-1" aria-labelledby="softDrinkModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
<div class="modal-header" style="background: linear-gradient(45deg, #0FAA39, #0D9232); color: white; border-radius: 15px 15px 0 0;">
<h5 class="modal-title" id="softDrinkModalLabel">Select Your Drink</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="padding: 20px;">
<div class="text-center mb-4">
<img id="soft-drink-image" class="img-fluid rounded mb-3" alt="Soft Drink Image" style="max-height: 150px; width: auto; object-fit: contain;">
<h5 id="soft-drink-name" class="fw-bold" style="color: #333;"></h5>
<p id="soft-drink-price" class="text-muted" style="font-size: 1.1rem;"></p>
</div>
<div class="d-flex justify-content-center align-items-center mb-
4">
<div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
<button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;" aria-label="Decrease Drink Quantity">-</button>
<input type="text" class="form-control text-center mx-2" id="soft-drink-quantity" value="1" readonly style="width: 60px; font-weight: bold; font-size: 1.1rem;" aria-label="Drink Quantity">
<button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;" aria-label="Increase Drink Quantity">+</button>
</div>
</div>
</div>
<div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
<button type="button" class="btn btn-primary" onclick="addSoftDrinkToCart()" style="width: 200px; padding: 12px; font-size: 1.1rem; background-color: #0FAA39; border-color: #0FAA39;" aria-label="Add Drink to Cart">Add to Cart</button>
</div>
</div>
</div>
</div>
<!-- Mic Popup -->
<div class="mic-popup" id="micPopup" role="alert" aria-live="polite">
<div class="mic-popup-icon">
<i class="bi bi-mic-fill"></i>
</div>
<div class="mic-popup-text" id="micPopupText">Listening...</div>
<button class="mic-popup-cancel" id="micPopupCancel" aria-label="Cancel Voice Search">Cancel</button>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="confirmationModal" tabindex="-1" aria-labelledby="confirmationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmationModalLabel">Confirm Action</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p id="confirmationMessage"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" aria-label="Cancel">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmAction" aria-label="Confirm">Confirm</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
let isProcessingRequest = false;
let currentSoftDrinkButton = null;
const menuItems = [
{% for section, items in ordered_menu.items() %}
{% for item in items %}
"{{ item.Name | default('Unnamed Item') | e }}",
{% endfor %}
{% endfor %}
];
const sections = [
{% for section in ordered_menu.keys() %}
"{{ section | e }}",
{% endfor %}
];
const ingredientsList = [
"Basmati Rice", "Bell Pepper", "Biryani Masala", "Butter", "Capsicum", "Cauliflower",
"Chickpea Flour (Besan)", "Chickpea Flour (for batter)", "Chickpeas (Channa)", "Chili Powder",
"Chili Sauce", "Coconut Milk", "Coriander Powder", "Cornflour", "Cream", "Cumin Powder",
"Cumin Seeds", "Curd (Yogurt)", "Curry Leaves", "Fish (e.g., King Fish or Salmon)",
"Fresh Coriander Leaves", "Garam Masala", "Garlic", "Ghee (Clarified Butter)", "Ginger",
"Ginger-Garlic Paste", "Goat Meat (Mutton)", "Green Chilies", "Honey",
"Kasuri Methi (dried fenugreek leaves)", "Lemon Juice", "Mango Puree", "Mint Leaves",
"Mixed Vegetables (Carrot, Peas, Potato, Cauliflower)", "Mixed Vegetables (Carrot, Peas, Potato)",
"Mustard Seeds", "Mutton (Goat Meat)", "Oil", "Oil (for frying)", "Onion",
"Paneer (Indian Cottage Cheese)", "Peas", "Potatoes", "Prawns", "Red Chili Powder",
"Rice Flour", "Saffron", "Salt", "Soy Sauce", "Spring Onion", "Tamarind (for sourness)",
"Tomato Ketchup", "Tomatoes", "Turmeric Powder", "Vinegar", "Water", "Wheat Flour (for dough)",
"Whole Wheat Flour", "Yogurt (Curd)"
];
const dishDatabase = {
"Aloo Paratha": {
ingredients: "Wheat flour, potatoes, spices (cumin, coriander), ghee",
description: "Stuffed potato flatbread cooked with ghee, a North Indian breakfast favorite.",
price: 8.99
},
"Appam": {
ingredients: "Rice flour, coconut milk, yeast, sugar",
description: "Soft and lacy Kerala rice pancakes, perfect with stew or curry.",
price: 7.49
},
"Aamras": {
ingredients: "Ripe mangoes, sugar, cardamom",
description: "Sweet mango pulp dessert, a summer specialty from Rajasthan and Gujarat.",
price: 6.99
},
"Biryani": {
ingredients: "Basmati rice, meat (chicken/mutton), spices (saffron, bay leaf), yogurt, fried onions",
description: "Fragrant layered rice dish with spiced meat, a celebration favorite.",
price: 14.99
},
"Butter Chicken": {
ingredients: "Chicken, tomato puree, butter, cream, garam masala",
description: "Creamy Punjabi curry with tender chicken pieces in rich tomato sauce.",
price: 15.99
},
"Bhel Puri": {
ingredients: "Puffed rice, sev, tamarind chutney, onions, tomatoes",
description: "Mumbai's famous sweet-sour-spicy street snack with crunchy textures.",
price: 6.49
},
"Chole Bhature": {
ingredients: "Chickpeas, flour (for bhature), onions, tomatoes, chole masala",
description: "Spicy chickpea curry with deep-fried bread, a North Indian classic.",
price: 9.99
},
"Chicken Chettinad": {
ingredients: "Chicken, coconut, black pepper, fennel, curry leaves",
description: "Fiery Tamil Nadu chicken curry with aromatic spices and coconut.",
price: 16.99
},
"Dosa": {
ingredients: "Rice, urad dal, salt (fermented batter)",
description: "Crispy South Indian crepe, typically served with sambar and chutney.",
price: 8.99
},
"Dal Tadka": {
ingredients: "Toor dal, garlic, cumin, ghee, tomatoes",
description: "Tempered lentil dish with aromatic spices, a staple across India.",
price: 7.99
},
"Dhokla": {
ingredients: "Gram flour, yogurt, eno fruit salt, mustard seeds",
description: "Steamed Gujarati snack with spongy texture and mild sweet-sour taste.",
price: 6.99
},
"Egg Curry": {
ingredients: "Boiled eggs, onion-tomato gravy, turmeric, coriander powder",
description: "Hard-boiled eggs in spiced gravy, popular in Bengal and Kerala.",
price: 9.49
},
"Fish Curry": {
ingredients: "Fish (pomfret/rohu), coconut milk, tamarind, curry leaves",
description: "Goan/Kerala style fish in tangy coconut gravy with spices.",
price: 17.99
},
"Gulab Jamun": {
ingredients: "Khoya, flour, sugar syrup, cardamom",
description: "Deep-fried milk dumplings soaked in rose-scented sugar syrup.",
price: 5.99
},
"Gajar ka Halwa": {
ingredients: "Carrots, milk, sugar, ghee, nuts",
description: "Slow-cooked carrot pudding with milk and dry fruits, winter special.",
price: 7.49
},
"Hyderabadi Biryani": {
ingredients: "Basmati rice, marinated meat, saffron, fried onions, mint",
description: "Famous layered biryani from Hyderabad with dum cooking technique.",
price: 18.99
},
"Idli": {
ingredients: "Rice, urad dal, fenugreek seeds (fermented batter)",
description: "Steamed rice cakes, South India's healthiest breakfast option.",
price: 6.99
},
"Jalebi": {
ingredients: "Maida, yogurt, sugar syrup, saffron",
description: "Crispy orange swirls soaked in sugar syrup, served hot.",
price: 5.49
},
"Kaju Katli": {
ingredients: "Cashew paste, sugar, ghee, cardamom",
description: "Diamond-shaped cashew fudge, a festive favorite.",
price: 8.99
},
"Korma": {
ingredients: "Meat (chicken/lamb), yogurt, cashew paste, cream",
description: "Mughlai creamy curry with mild spices and nutty flavor.",
price: 16.49
},
"Litti Chokha": {
ingredients: "Whole wheat flour, sattu (roasted gram flour), eggplant, potatoes",
description: "Bihar's rustic roasted dough balls with spicy vegetable mash.",
price: 10.99
},
"Masala Dosa": {
ingredients: "Dosa batter, potato filling, mustard seeds, curry leaves",
description: "Crispy crepe stuffed with spiced potatoes, served with chutney.",
price: 9.99
},
"Nihari": {
ingredients: "Beef/mutton, bone marrow, ginger-garlic, wheat flour (for thickening)",
description: "Slow-cooked meat stew, a Mughlai breakfast delicacy.",
price: 17.99
},
"Onion Pakoda": {
ingredients: "Besan (gram flour), onions, chili powder, carom seeds",
description: "Crispy onion fritters, perfect monsoon snack with tea.",
price: 5.99
},
"Pani Puri": {
ingredients: "Puris, tamarind water, mashed potatoes, chickpeas",
description: "Hollow crispy puris filled with spicy-sour water and fillings.",
price: 6.99
},
"Pav Bhaji": {
ingredients: "Mixed veggies (potatoes, peas, capsicum), butter, pav bread",
description: "Mumbai's famous buttery vegetable mash with soft bread rolls.",
price: 10.49
},
"Rajma Chawal": {
ingredients: "Kidney beans, onions, tomatoes, cumin, rice",
description: "Comfort food from Punjab - red kidney beans curry with rice.",
price: 11.99
},
"Sambar": {
ingredients: "Toor dal, tamarind, sambar powder, veggies (drumstick, pumpkin)",
description: "South Indian lentil stew with vegetables and tangy tamarind.",
price: 8.49
},
"Tandoori Chicken": {
ingredients: "Chicken, yogurt, red chili, tandoori masala, lemon",
description: "Clay oven-roasted chicken marinated in yogurt and spices.",
price: 15.99
},
"Vada Pav": {
ingredients: "Potatoes, pav bread, garlic chutney, green chutney",
description: "Mumbai's vegetarian burger - spicy potato patty in bread.",
price: 6.99
},
"Wada (Medu Vada)": {
ingredients: "Urad dal, black pepper, curry leaves, coconut chutney",
description: "South Indian savory donuts made from lentil batter.",
price: 7.49
},
"Xacuti": {
ingredients: "Chicken/lamb, roasted coconut, poppy seeds, Kashmiri chilies",
description: "Goan coconut-based curry with complex spice flavors.",
price: 16.99
},
"Yakhni Pulao": {
ingredients: "Mutton, yogurt, fennel, ghee, basmati rice",
description: "Kashmiri aromatic rice cooked in meat stock with mild spices.",
price: 15.99
},
"Zunka": {
ingredients: "Besan (gram flour), onions, mustard seeds, turmeric",
description: "Maharashtra's spiced gram flour dish, typically served with bhakri.",
price: 7.99
}
};
function sanitizeInput(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}
function addToCartLocalStorage(payload) {
let cart = JSON.parse(localStorage.getItem('cart')) || [];
const existingItem = cart.find(item =>
item.itemName === payload.itemName &&
item.instructions === payload.instructions &&
JSON.stringify(item.addons) === JSON.stringify(payload.addons)
);
if (existingItem) {
existingItem.quantity += payload.quantity;
} else {
cart.push(payload);
}
localStorage.setItem('cart', JSON.stringify(cart));
return cart;
}
function removeFromCartLocalStorage(itemName, quantityToRemove, instructions, addons) {
let cart = JSON.parse(localStorage.getItem('cart')) || [];
const itemIndex = cart.findIndex(item =>
item.itemName === itemName &&
item.instructions === instructions &&
JSON.stringify(item.addons) === JSON.stringify(addons)
);
if (itemIndex !== -1) {
if (quantityToRemove >= cart[itemIndex].quantity) {
cart.splice(itemIndex, 1);
} else {
cart[itemIndex].quantity -= quantityToRemove;
}
}
localStorage.setItem('cart', JSON.stringify(cart));
return cart;
}
function getCartLocalStorage() {
return JSON.parse(localStorage.getItem('cart')) || [];
}
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function showConfirmation(message, callback) {
document.getElementById('confirmationMessage').innerText = message;
const confirmBtn = document.getElementById('confirmAction');
confirmBtn.onclick = function() {
callback();
bootstrap.Modal.getInstance(document.getElementById('confirmationModal')).hide();
};
const modal = new bootstrap.Modal(document.getElementById('confirmationModal'));
modal.show();
}
function showSoftDrinkModal(button) {
currentSoftDrinkButton = button;
const buttonContainer = button.closest('.button-container');
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
const itemPrice = buttonContainer.getAttribute('data-item-price');
const itemImage = buttonContainer.getAttribute('data-item-image');
document.getElementById('soft-drink-name').textContent = itemName;
document.getElementById('soft-drink-price').textContent = `$${itemPrice}`;
document.getElementById('soft-drink-quantity').value = '1';
const softDrinkImage = document.getElementById('soft-drink-image');
softDrinkImage.src = itemImage || '/static/placeholder.jpg';
const modal = new bootstrap.Modal(document.getElementById('softDrinkModal'));
modal.show();
}
function addSoftDrinkToCart() {
if (!currentSoftDrinkButton) return;
const buttonContainer = currentSoftDrinkButton.closest('.button-container');
const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
if (quantity < 1 || quantity > 99) {
alert('Quantity must be between 1 and 99.');
return;
}
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
const itemImage = buttonContainer.getAttribute('data-item-image');
const section = sanitizeInput(buttonContainer.getAttribute('data-item-section'));
const selectedCategory = sanitizeInput(buttonContainer.getAttribute('data-item-category'));
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: [],
instructions: '',
quantity: quantity,
timestamp: new Date().toISOString()
};
fetch('/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(cartPayload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Item added to cart successfully!');
updateCartUI(data.cart);
bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
} else {
throw new Error(data.error || 'Failed to add item to cart');
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
alert('Error adding item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
bootstrap.Modal.getInstance(document.getElementById('softDrinkModal')).hide();
});
}
function updateCartUI(cart) {
if (!Array.isArray(cart)) {
console.error('Invalid cart data:', cart);
return;
}
let totalQuantity = 0;
cart.forEach(item => {
totalQuantity += item.quantity;
});
const cartItemCount = document.getElementById('cart-item-count');
if (cartItemCount) {
cartItemCount.innerText = totalQuantity;
cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
}
}
document.addEventListener('DOMContentLoaded', function () {
const avatarContainer = document.querySelector('.avatar-dropdown-container');
const dropdownMenu = document.querySelector('.dropdown-menu');
const avatarIcon = document.getElementById('avatarIcon');
const avatarUpload = document.getElementById('avatarUpload');
const deleteAvatar = document.getElementById('deleteAvatar');
const viewAvatar = document.getElementById('viewAvatar');
function loadAvatar() {
const savedAvatar = localStorage.getItem('userAvatar');
const avatarImage = avatarIcon.querySelector('.avatar-image');
if (savedAvatar && !avatarImage) {
const img = document.createElement('img');
img.src = savedAvatar;
img.alt = 'User Avatar';
img.className = 'avatar-image';
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
avatarIcon.innerHTML = '';
avatarIcon.appendChild(img);
ensureAvatarOptions();
} else if (!savedAvatar && avatarImage) {
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
removeAvatarOptions();
}
}
function ensureAvatarOptions() {
if (!document.querySelector('#deleteAvatar')) {
const deleteItem = document.createElement('div');
deleteItem.className = 'dropdown-item delete-item';
deleteItem.id = 'deleteAvatar';
deleteItem.innerText = 'Delete Image';
dropdownMenu.insertBefore(deleteItem, dropdownMenu.firstChild);
addDeleteListener(deleteItem);
}
if (!document.querySelector('#viewAvatar')) {
const viewItem = document.createElement('div');
viewItem.className = 'dropdown-item view-item';
viewItem.id = 'viewAvatar';
viewItem.innerText = 'View Avatar';
const firstChild = dropdownMenu.firstChild;
if (firstChild) dropdownMenu.insertBefore(viewItem, firstChild.nextSibling);
addViewListener(viewItem);
}
}
function removeAvatarOptions() {
const deleteItem = document.getElementById('deleteAvatar');
const viewItem = document.getElementById('viewAvatar');
if (deleteItem) deleteItem.remove();
if (viewItem) viewItem.remove();
}
function saveAvatar(imageUrl) {
localStorage.setItem('userAvatar', imageUrl);
}
function clearAvatar() {
localStorage.removeItem('userAvatar');
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
removeAvatarOptions();
}
avatarIcon.addEventListener('click', function(event) {
event.stopPropagation();
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
});
avatarUpload.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const maxSize = 15 * 1024 * 1024; // 15MB in bytes
if (file.size > maxSize) {
alert('Image size must not exceed 15MB.');
this.value = '';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const base64Image = e.target.result;
fetch('/upload_avatar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: base64Image })
})
.then(response => {
if (!response.ok) throw new Error('Upload failed');
return response.json();
})
.then(data => {
if (data.success) {
const img = document.createElement('img');
img.src = data.image || '/static/default-avatar.jpg';
img.alt = 'User Avatar';
img.className = 'avatar-image';
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
avatarIcon.innerHTML = '';
avatarIcon.appendChild(img);
saveAvatar(data.image);
ensureAvatarOptions();
dropdownMenu.style.display = 'none';
} else {
throw new Error(data.error || 'Unknown error');
}
})
.catch(error => {
console.error('Upload error:', error);
alert('Error uploading image. Using default image as fallback.');
const img = document.createElement('img');
img.src = '/static/default-avatar.jpg';
img.alt = 'User Avatar';
img.className = 'avatar-image';
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
avatarIcon.innerHTML = '';
avatarIcon.appendChild(img);
saveAvatar('/static/default-avatar.jpg');
ensureAvatarOptions();
dropdownMenu.style.display = 'none';
});
};
reader.onerror = function() {
alert('Error reading the image file.');
};
reader.readAsDataURL(file);
}
});
function addDeleteListener(deleteElement) {
deleteElement.addEventListener('click', function() {
showConfirmation('Are you sure you want to delete your avatar?', function() {
fetch('/delete_avatar', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) throw new Error('Delete failed');
return response.json();
})
.then(data => {
if (data.success) {
clearAvatar();
dropdownMenu.style.display = 'none';
} else {
throw new Error(data.error || 'Unknown error');
}
})
.catch(error => {
console.error('Delete error:', error);
alert('Error deleting image. Cleared locally.');
clearAvatar();
dropdownMenu.style.display = 'none';
});
});
});
}
function addViewListener(viewElement) {
viewElement.addEventListener('click', function() {
const avatarImage = avatarIcon.querySelector('.avatar-image');
if (avatarImage) {
const modalImg = document.getElementById('avatar-modal-img');
modalImg.src = avatarImage.src;
const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
modal.show();
} else {
alert('No avatar image to view.');
}
dropdownMenu.style.display = 'none';
});
}
if (deleteAvatar) addDeleteListener(deleteAvatar);
if (viewAvatar) addViewListener(viewAvatar);
loadAvatar();
document.addEventListener('click', function(event) {
if (!avatarContainer.contains(event.target)) {
dropdownMenu.style.display = 'none';
}
});
const menuCards = document.querySelectorAll('.menu-card');
const menuVideos = document.querySelectorAll('.menu-video');
const cardObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, {
root: null,
rootMargin: '0px',
threshold: 0.1
});
const videoObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target;
const src = video.getAttribute('data-src');
if (src && !video.querySelector('source[src="' + src + '"]')) {
const source = video.querySelector('source');
source.src = src;
video.load();
}
video.classList.add('loaded');
observer.unobserve(video);
}
});
}, {
root: null,
rootMargin: '200px',
threshold: 0.01
});
menuCards.forEach(card => cardObserver.observe(card));
menuVideos.forEach(video => videoObserver.observe(video));
const toggleLinks = document.querySelectorAll('.toggle-details');
toggleLinks.forEach(link => {
link.addEventListener('click', function () {
const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
const detailsDiv = document.getElementById(`details-${itemName}`);
const isCurrentlyShown = detailsDiv.classList.contains('show');
document.querySelectorAll('.item-details.show').forEach(otherDetails => {
if (otherDetails !== detailsDiv) {
otherDetails.classList.remove('show');
const otherLink = otherDetails.previousElementSibling.querySelector('.toggle-details');
if (otherLink) {
otherLink.innerText = 'Show Details';
otherLink.setAttribute('aria-expanded', 'false');
}
}
});
if (!isCurrentlyShown) {
detailsDiv.classList.add('show');
this.innerText = 'Hide Details';
this.setAttribute('aria-expanded', 'true');
} else {
detailsDiv.classList.remove('show');
this.innerText = 'Show Details';
this.setAttribute('aria-expanded', 'false');
}
});
});
const categoryButtons = document.querySelectorAll('.category-button');
const categoryForm = document.getElementById('categoryForm');
const selectedCategoryInput = document.getElementById('selectedCategoryInput');
if (!selectedCategoryInput.value) {
selectedCategoryInput.value = "All";
const allBtn = document.querySelector('.category-button[data-category="All"]');
if (allBtn) allBtn.classList.add('selected');
}
categoryButtons.forEach(button => {
button.addEventListener('click', function () {
categoryButtons.forEach(btn => btn.classList.remove('selected'));
this.classList.add('selected');
selectedCategoryInput.value = this.getAttribute('data-category');
categoryForm.submit();
});
});
const searchBar = document.getElementById('searchBar');
const suggestionsContainer = document.getElementById('autocompleteSuggestions');
const debouncedFilterMenu = debounce(filterMenu, 300);
// Redirect to search page on click
searchBar.addEventListener('click', function (event) {
event.stopPropagation();
window.location.href = '/search';
});
searchBar.addEventListener('input', function () {
const input = sanitizeInput(this.value.trim().toLowerCase());
suggestionsContainer.innerHTML = '';
suggestionsContainer.style.display = 'none';
if (input) {
const filteredItems = menuItems.filter(item =>
item.toLowerCase().includes(input)
);
if (filteredItems.length > 0) {
filteredItems.forEach(item => {
const suggestionDiv = document.createElement('div');
suggestionDiv.classList.add('suggestion-item');
suggestionDiv.innerText = item;
suggestionDiv.addEventListener('click', function () {
searchBar.value = item;
suggestionsContainer.style.display = 'none';
debouncedFilterMenu();
});
suggestionsContainer.appendChild(suggestionDiv);
});
suggestionsContainer.style.display = 'block';
}
}
debouncedFilterMenu();
});
document.addEventListener('click', function (event) {
if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
suggestionsContainer.style.display = 'none';
}
});
// Dish name autocomplete functionality
const dishNameInput = document.getElementById('custom-dish-name');
const dishIngredients = document.getElementById('custom-dish-ingredients');
const dishDescription = document.getElementById('custom-dish-description');
const dishPrice = document.getElementById('custom-dish-price');
const dishSuggestions = document.getElementById('dishNameSuggestions');
dishNameInput.addEventListener('input', function() {
const input = this.value.trim().toLowerCase();
dishSuggestions.innerHTML = '';
dishSuggestions.style.display = 'none';
if (input.length > 0) {
const matches = Object.keys(dishDatabase).filter(dish =>
dish.toLowerCase().includes(input)
);
if (matches.length > 0) {
matches.slice(0, 5).forEach(dish => {
const suggestion = document.createElement('div');
suggestion.classList.add('suggestion-item');
suggestion.textContent = dish;
suggestion.addEventListener('click', function() {
dishNameInput.value = dish;
// Auto-fill the other fields
if (dishDatabase[dish]) {
dishIngredients.value = dishDatabase[dish].ingredients;
dishDescription.value = dishDatabase[dish].description;
dishPrice.value = dishDatabase[dish].price.toFixed(2);
}
dishSuggestions.style.display = 'none';
});
dishSuggestions.appendChild(suggestion);
});
dishSuggestions.style.display = 'block';
}
}
});
// Close suggestions when clicking elsewhere
document.addEventListener('click', function(e) {
if (e.target !== dishNameInput) {
dishSuggestions.style.display = 'none';
}
});
// Allow manual editing after auto-fill
dishIngredients.addEventListener('focus', function() {
if (!this.value && dishNameInput.value && dishDatabase[dishNameInput.value]) {
this.value = dishDatabase[dishNameInput.value].ingredients;
}
});
dishDescription.addEventListener('focus', function() {
if (!this.value && dishNameInput.value && dishDatabase[dishNameInput.value]) {
this.value = dishDatabase[dishNameInput.value].description;
}
});
const customDishForm = document.getElementById('customDishForm');
if (customDishForm) {
customDishForm.addEventListener('submit', function(event) {
const dishName = document.getElementById('custom-dish-name').value.trim();
const description = document.getElementById('custom-dish-description').value.trim();
if (!dishName || !description) {
event.preventDefault();
alert('Please fill in both the dish name and description.');
return;
}
event.preventDefault();
fetch('/customdish/generate_custom_dish', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'name': dishName,
'description': description,
'ingredients': document.getElementById('custom-dish-ingredients').value,
'price': document.getElementById('custom-dish-price').value
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Custom dish submitted successfully!');
window.location.reload();
} else {
alert('Failed to submit custom dish: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error submitting custom dish:', error);
alert('Error submitting custom dish. Please try again.');
});
});
}
fetch('/cart/get')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
updateCartUI(data.cart);
} else {
console.error('Failed to fetch cart:', data.error);
const cart = getCartLocalStorage();
updateCartUI(cart);
}
})
.catch(err => {
console.error('Error fetching cart:', err);
const cart = getCartLocalStorage();
updateCartUI(cart);
});
const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
preloadedVideos.forEach(link => {
const video = document.createElement('video');
video.src = link.href;
video.preload = 'auto';
});
const decreaseBtn = document.getElementById('decreaseQuantity');
const increaseBtn = document.getElementById('increaseQuantity');
const quantityInput = document.getElementById('quantityInput');
decreaseBtn.addEventListener('click', function () {
let currentQuantity = parseInt(quantityInput.value);
if (currentQuantity > 1) {
currentQuantity--;
quantityInput.value = currentQuantity;
}
});
increaseBtn.addEventListener('click', function () {
let currentQuantity = parseInt(quantityInput.value);
currentQuantity++;
quantityInput.value = currentQuantity;
});
const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
softDrinkDecreaseBtn.addEventListener('click', function() {
let currentQuantity = parseInt(softDrinkQuantityInput.value);
if (currentQuantity > 1) {
currentQuantity--;
softDrinkQuantityInput.value = currentQuantity;
}
});
softDrinkIncreaseBtn.addEventListener('click', function() {
let currentQuantity = parseInt(softDrinkQuantityInput.value);
if (currentQuantity < 1000) {
currentQuantity++;
softDrinkQuantityInput.value = currentQuantity;
}
});
// Mic Popup Functionality
const micIcon = document.getElementById('micIcon');
const micUnsupported = document.getElementById('micUnsupported');
const micPopup = document.getElementById('micPopup');
const micPopupText = document.getElementById('micPopupText');
const micPopupCancel = document.getElementById('micPopupCancel');
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = true;
recognition.lang = 'en-US';
recognition.onstart = () => {
micIcon.classList.add('active');
micPopup.classList.add('active');
micPopupText.textContent = 'Listening...';
};
recognition.onresult = (event) => {
let interimTranscript = '';
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript;
} else {
interimTranscript += transcript;
}
}
if (interimTranscript) {
micPopupText.textContent = interimTranscript;
}
if (finalTranscript) {
searchBar.value = sanitizeInput(finalTranscript.trim());
debouncedFilterMenu();
micPopup.classList.remove('active');
}
};
recognition.onend = () => {
micIcon.classList.remove('active');
if (micPopup.classList.contains('active')) {
setTimeout(() => {
micPopup.classList.remove('active');
}, 1000);
}
};
recognition.onerror = (event) => {
micIcon.classList.remove('active');
micPopupText.textContent = 'Error: ' + event.error;
setTimeout(() => {
micPopup.classList.remove('active');
}, 2000);
console.error('Speech error:', event.error);
};
micIcon.addEventListener('click', () => {
try {
recognition.start();
} catch (e) {
micPopupText.textContent = 'Error starting microphone';
setTimeout(() => {
micPopup.classList.remove('active');
}, 2000);
console.error('Recognition start error:', e);
}
});
micPopupCancel.addEventListener('click', () => {
recognition.stop();
micPopup.classList.remove('active');
micIcon.classList.remove('active');
});
} else {
micIcon.style.display = 'none';
micUnsupported.style.display = 'block';
}
// Highlight and open modal for item from search redirect
const highlightItem = "{{ highlight_item | e }}";
if (highlightItem) {
const itemElement = document.querySelector(`[data-item-name="${highlightItem}"]`);
if (itemElement) {
itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
itemElement.style.backgroundColor = '#FFFFE0'; // Light yellow highlight
setTimeout(() => {
itemElement.style.backgroundColor = ''; // Remove highlight after 2 seconds
}, 2000);
// Find the ADD button and trigger the appropriate modal
const addButton = itemElement.querySelector('.btn-primary');
const buttonContainer = addButton.closest('.button-container');
const section = buttonContainer.getAttribute('data-item-section');
if (section === 'Soft Drinks') {
showSoftDrinkModal(addButton);
} else {
const name = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
const price = buttonContainer.getAttribute('data-item-price');
const image = buttonContainer.getAttribute('data-item-image');
const category = buttonContainer.getAttribute('data-item-category');
// Use data from the button's onclick attribute or fetch from server
const onclickAttr = addButton.getAttribute('onclick');
if (onclickAttr) {
const argsMatch = onclickAttr.match(/showItemDetails\((.*?)\)/);
if (argsMatch) {
const args = argsMatch[1].split(',').map(arg => arg.trim().replace(/['"]/g, ''));
const [name, price, image, description, ingredients, nutrition, allergens, section, category] = args;
showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, category);
}
} else {
// Fallback if onclick isn't sufficient
showItemDetails(name, price, image, 'No description available', 'Not specified', 'Not available', 'None listed', section, category);
}
}
}
}
});
function filterMenu() {
const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
const sections = document.querySelectorAll('h3');
const items = document.querySelectorAll('.menu-card');
let matchedSections = new Set();
items.forEach(item => {
const itemName = item.querySelector('.card-title').innerText.toLowerCase();
const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
item.style.display = 'block';
item.classList.add('visible');
matchedSections.add(item.closest('.row'));
} else {
item.style.display = 'none';
}
});
sections.forEach(section => {
const sectionRow = section.nextElementSibling;
if (matchedSections.has(sectionRow)) {
section.style.display = 'block';
sectionRow.style.display = 'flex';
} else {
section.style.display = 'none';
sectionRow.style.display = 'none';
}
});
if (!input) {
sections.forEach(section => {
section.style.display = 'block';
section.nextElementSibling.style.display = 'flex';
});
items.forEach(item => {
item.style.display = 'block';
item.classList.add('visible');
});
}
}
function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
document.getElementById('modal-name').innerText = name;
document.getElementById('modal-price').innerText = `$${price}`;
const modalImg = document.getElementById('modal-img');
modalImg.src = image || '/static/placeholder.jpg';
document.getElementById('modal-description').innerText = description || 'No description available.';
document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
document.getElementById('modal-allergens').innerText = allergens || 'None listed';
document.getElementById('addons-list').innerHTML = 'Loading customization options...';
document.getElementById('modal-instructions').value = '';
const modalSectionEl = document.getElementById('modal-section');
modalSectionEl.setAttribute('data-section', section);
modalSectionEl.setAttribute('data-category', selectedCategory);
document.getElementById('quantityInput').value = 1;
fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
.then(response => response.json())
.then(data => {
const addonsList = document.getElementById('addons-list');
addonsList.innerHTML = '';
if (!data.success || !data.addons || data.addons.length === 0) {
addonsList.innerHTML = '<p>No customization options available.</p>';
return;
}
data.addons.forEach(addon => {
const sectionDiv = document.createElement('div');
sectionDiv.classList.add('addon-section');
const title = document.createElement('h6');
title.innerText = addon.name;
sectionDiv.appendChild(title);
const optionsContainer = document.createElement('div');
addon.options.forEach((option, index) => {
const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
const listItem = document.createElement('div');
listItem.classList.add('form-check');
listItem.innerHTML = `
<input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
<label class="form-check-label" for="${optionId}">
${option} ${addon.extra_charge ? `($${addon.extra_charge_amount})` : ''}
</label>
`;
optionsContainer.appendChild(listItem);
});
sectionDiv.appendChild(optionsContainer);
addonsList.appendChild(sectionDiv);
});
})
.catch(err => {
console.error('Error fetching add-ons:', err);
document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
});
}
document.addEventListener('click', function(event) {
if (event.target.classList.contains('addon-option')) {
handleAddonClick(event.target);
}
});
function handleAddonClick(checkbox) {
const groupName = checkbox.getAttribute('data-group');
const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it"].includes(groupName);
const checkboxes = document.querySelectorAll(`.addon-option[data-group="${groupName}"]`);
if (!isMultiSelectGroup) {
checkboxes.forEach(cb => {
if (cb !== checkbox) cb.checked = false;
});
}
}
function addToCartFromModal() {
const itemName = document.getElementById('modal-name').innerText;
const itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
const itemImage = document.getElementById('modal-img').src;
const instructions = document.getElementById('modal-instructions').value.trim();
const quantity = parseInt(document.getElementById('quantityInput').value);
const section = document.getElementById('modal-section').getAttribute('data-section');
const selectedCategory = document.getElementById('modal-section').getAttribute('data-category');
const addons = Array.from(document.querySelectorAll('.addon-option:checked')).map(cb => ({
name: cb.getAttribute('data-name'),
price: parseFloat(cb.getAttribute('data-price') || 0)
}));
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: addons,
instructions: instructions,
quantity: quantity
};
fetch('/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(cartPayload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Item added to cart successfully!');
updateCartUI(data.cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
modal.hide();
} else {
console.error('Failed to add item to cart:', data.error);
alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = document.getElementById('itemModal');
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
alert('Error adding item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = document.getElementById('itemModal');
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
});
}
</script>
</body>
</html>