lokesh341's picture
Update templates/menu.html
6ec77cb verified
raw
history blame
87.9 kB
<!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/[email protected]/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>
/* Same styles as previous response */
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;
}
.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;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.menu-card.visible {
opacity: 1;
animation: fadeIn 0.3s ease-in-out;
}
.menu-card.highlighted {
border: 3px solid #0FAA39;
box-shadow: 0 0 10px rgba(15, 170, 57, 0.5);
}
.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;
margin-left: 13px;
}
.addbutton .btn:hover {
background-color: #218838;
}
.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;
}
.dropdown-menu {
position: absolute;
right: 0;
top: 100%;
background-color: #fff8f0;
border-radius: 5px;
width: 220px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
display: none;
border: 1px solid #ffd8b1;
}
.dropdown-menu .dropdown-item {
padding: 12px 16px;
text-decoration: none;
color: #333;
border-bottom: 1px solid #ffd8b1;
display: block;
font-size: 15px;
transition: background-color 0.2s ease;
}
.dropdown-menu .dropdown-item:last-child {
border-bottom: none;
}
.dropdown-menu .dropdown-item:hover {
background-color: #ffe4c4;
color: #333;
}
.fixed-top-bar {
position: relative;
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;
}
.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;
}
.search-bar-container input::placeholder {
color: #888;
}
.search-bar-container input:focus {
border-bottom: 2px solid #FFA07A;
}
.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;
}
/* Addon Section */
.addon-section {
background-color: #fff;
border-radius: 10px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.2s ease;
}
.addon-section:hover {
transform: translateY(-2px);
}
.addon-section h6 {
margin: 0;
padding: 12px 15px;
font-size: 1.1rem;
font-weight: 600;
color: #fff;
background: linear-gradient(45deg, #FFA07A, #FFB347);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.addon-section h6::after {
content: '\25BC';
font-size: 0.9rem;
transition: transform 0.2s ease;
}
.addon-section.collapsed h6::after {
transform: rotate(180deg);
}
.addon-section .form-check {
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: space-between;
margin: 10px 15px;
padding: 0;
}
.addon-section .form-check-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #6c757d;
border-radius: 4px;
background-color: #fff;
position: relative;
margin-left: 10px;
cursor: pointer;
transition: all 0.2s ease;
}
.addon-section .form-check-input:checked {
background-color: #0FAA39;
border-color: #0FAA39;
}
.addon-section .form-check-input:checked::before {
content: '\2713';
font-size: 12px;
position: absolute;
top: 2px;
left: 4px;
color: white;
animation: checkmark 0.2s ease-in-out;
}
.addon-section .form-check-input:hover {
transform: scale(1.1);
border-color: #0FAA39;
}
.addon-section .form-check-input:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(15, 170, 57, 0.2);
}
.addon-section .form-check-label {
font-size: 0.95rem;
color: #343a40;
cursor: pointer;
flex: 1;
padding: 5px 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
text-align: left;
}
.addon-section .form-check-label:hover {
background-color: #e6f4ea;
}
.addon-section .form-check-label .extra-charge {
color: #FFA07A;
font-weight: 600;
margin-left: 8px;
}
.addon-options {
max-height: 500px;
opacity: 1;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
}
.addon-options.collapsed {
max-height: 0;
opacity: 0;
}
.addon-loading {
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
}
.addon-loading::after {
content: '';
width: 20px;
height: 20px;
border: 3px solid #0FAA39;
border-top: 3px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Soft Drinks Modal Styling */
#softDrinkModal .modal-dialog {
max-width: 400px;
margin: 1.75rem auto;
animation: fadeInModal 0.3s ease-out;
}
#softDrinkModal .modal-content {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background-color: #fff;
overflow: hidden;
}
#softDrinkModal .modal-header {
background: linear-gradient(45deg, #FFA07A, #FFB347);
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding: 12px 15px;
border-bottom: none;
}
#softDrinkModal .modal-title {
font-size: 1.3rem;
font-weight: 600;
color: #fff;
}
#softDrinkModal .modal-body {
padding: 20px;
text-align: center;
}
#softDrinkModal .modal-body img {
max-height: 160px;
width: 100%;
object-fit: cover;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
#softDrinkModal .modal-body h5 {
font-size: 1.4rem;
font-weight: 600;
color: #333333;
margin-bottom: 10px;
}
#softDrinkModal .modal-body p {
font-size: 1.2rem;
font-weight: 500;
color: #000000;
margin-bottom: 20px;
}
#softDrinkModal .modal-body p::before {
content: '$';
color: #FFA07A;
font-weight: 600;
margin-right: 2px;
}
#softDrinkModal .quantity-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
#softDrinkModal .quantity-controls .btn {
width: 32px;
height: 32px;
border-radius: 50%;
padding: 0;
font-size: 1.1rem;
line-height: 32px;
text-align: center;
background-color: #e9ecef;
border: none;
color: #333;
transition: background-color 0.2s ease, transform 0.1s ease;
}
#softDrinkModal .quantity-controls .btn:hover {
background-color: #d1d4d7;
transform: scale(1.1);
}
#softDrinkModal .quantity-controls .btn:active {
transform: scale(0.95);
}
#softDrinkModal .quantity-controls input {
width: 50px;
height: 32px;
text-align: center;
font-size: 1rem;
font-weight: 600;
border: 1px solid #ced4da;
border-radius: 6px;
background-color: #f8f9fa;
}
#softDrinkModal .modal-footer {
padding: 15px;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
}
#softDrinkModal .modal-footer .btn-primary {
background-color: #0FAA39;
border-color: #0FAA39;
padding: 12px 30px;
font-size: 1.1rem;
font-weight: 600;
border-radius: 8px;
transition: background-color 0.2s ease, transform 0.1s ease;
width: auto;
}
#softDrinkModal .modal-footer .btn-primary:hover {
background-color: #0D9232;
transform: scale(1.05);
}
#softDrinkModal .modal-footer .btn-primary:active {
background-color: #0B7A29;
transform: scale(0.98);
}
#softDrinkModal .modal-footer .quantity-controls-footer {
display: flex;
gap: 12px;
}
#softDrinkModal .modal-footer .quantity-controls-footer .btn {
width: 32px;
height: 32px;
border-radius: 50%;
padding: 0;
font-size: 1.1rem;
line-height: 32px;
text-align: center;
background-color: #e9ecef;
border: none;
color: #333;
transition: background-color 0.2s ease, transform 0.1s ease;
}
#softDrinkModal .modal-footer .quantity-controls-footer .btn:hover {
background-color: #d1d4d7;
transform: scale(1.1);
}
#softDrinkModal .modal-footer .quantity-controls-footer .btn:active {
transform: scale(0.95);
}
/* Toggle Styling */
.toggle-container {
display: inline-flex;
align-items: center;
margin: 0 15px;
gap: 8px;
}
.custom-toggle {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 40px;
height: 20px;
background: #e9ecef;
border-radius: 20px;
position: relative;
cursor: pointer;
outline: none;
transition: background-color 0.2s ease;
}
.custom-toggle:checked {
background: #0FAA39;
}
.custom-toggle::before {
content: '';
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
top: 2px;
left: 2px;
transition: transform 0.2s ease;
}
.custom-toggle:checked::before {
transform: translateX(20px);
}
.toggle-container label {
font-size: 1rem;
font-weight: 500;
color: #333;
cursor: pointer;
}
#filter-form {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
#filter-form .form-label {
font-size: 1.2rem;
color: #333;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 2000;
}
.toast {
background-color: #dc3545;
color: white;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.3s ease;
}
.toast.show {
opacity: 1;
}
.search-suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-height: 200px;
overflow-y: auto;
display: none;
z-index: 1000;
}
.search-suggestions .suggestion-item {
padding: 10px;
cursor: pointer;
font-size: 14px;
color: #333;
}
.search-suggestions .suggestion-item:hover {
background-color: #e6f4ea;
}
@keyframes checkmark {
from { transform: scale(0); }
to { transform: scale(1); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInModal {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
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;
}
.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: 15px;
}
.modal-body #modal-addons h5,
.modal-body #first-row h6 {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 15px;
color: #343a40;
}
.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 {
background-color: #f8f9fa;
border-radius: 8px;
padding: 10px;
margin: 10px 15px;
display: none;
}
.item-details.show {
display: block;
}
.item-details h6 {
font-size: 0.9rem;
font-weight: bold;
margin-bottom: 5px;
color: #333333;
}
.item-details p {
font-size: 0.85rem;
margin-bottom: 5px;
color: #333;
}
.item-details .nutritional-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
background-color: #e9ecef;
padding: 10px;
border-radius: 5px;
font-size: 0.85rem;
line-height: 1.5;
}
.toggle-details {
cursor: pointer;
color: #0FAA39;
font-size: 0.9rem;
margin-left: 15px;
margin-bottom: 10px;
display: inline-block;
}
.toggle-details:hover {
text-decoration: underline;
}
.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;
}
#custom-dish-form .btn-primary {
position: absolute;
right: 15px;
bottom: 15px;
width: auto;
padding: 10px 20px;
}
.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;
}
.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;
}
@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: 220px;
}
.dropdown-menu .dropdown-item {
padding: 12px 16px;
font-size: 15px;
}
.modal-dialog {
max-width: 90%;
margin: 10px auto;
}
.modal-header {
padding: 8px 12px;
}
.modal-title {
font-size: 14px;
}
.modal-body {
max-height: 50vh;
padding: 12px;
}
.modal-body #modal-img {
max-height: 150px;
width: 100%;
max-width: 150px;
margin: 0 auto 8px;
display: block;
}
.modal-body #modal-name {
font-size: 18px;
margin-bottom: 5px;
}
.modal-body #modal-price {
font-size: 14px;
margin-bottom: 8px;
}
.modal-body #modal-description {
font-size: 12px;
margin-bottom: 10px;
}
.modal-body .nutritional-info {
font-size: 10px;
margin-bottom: 5px;
}
.modal-body #modal-addons h5,
.modal-body #first-row h6 {
font-size: 1rem;
margin-bottom: 10px;
}
.modal-footer {
padding: 8px;
}
.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;
}
.addon-section {
margin-bottom: 10px;
}
.addon-section h6 {
font-size: 1rem;
padding: 10px 12px;
}
.addon-section .form-check {
margin: 8px 12px;
}
.addon-section .form-check-input {
width: 18px;
height: 18px;
margin-left: 8px;
}
.addon-section .form-check-input:checked::before {
font-size: 10px;
top: 2px;
left: 3px;
}
.addon-section .form-check-label {
font-size: 0.85rem;
padding: 4px 6px;
}
.addon-section .form-check-label .extra-charge {
margin-left: 6px;
font-size: 0.8rem;
}
#softDrinkModal .modal-dialog {
max-width: 90%;
}
#softDrinkModal .modal-content {
border-radius: 10px;
}
#softDrinkModal .modal-header {
padding: 10px 12px;
}
#softDrinkModal .modal-title {
font-size: 1.2rem;
}
#softDrinkModal .modal-body {
padding: 15px;
}
#softDrinkModal .modal-body img {
max-height: 130px;
margin-bottom: 12px;
}
#softDrinkModal .modal-body h5 {
font-size: 1.2rem;
margin-bottom: 8px;
}
#softDrinkModal .modal-body p {
font-size: 1rem;
margin-bottom: 15px;
}
#softDrinkModal .quantity-controls .btn {
width: 28px;
height: 28px;
font-size: 1rem;
line-height: 28px;
}
#softDrinkModal .quantity-controls input {
width: 45px;
height: 28px;
font-size: 0.9rem;
}
#softDrinkModal .modal-footer {
padding: 10px;
}
#softDrinkModal .modal-footer .btn-primary {
padding: 10px 25px;
font-size: 1rem;
}
#softDrinkModal .modal-footer .quantity-controls-footer .btn {
width: 28px;
height: 28px;
font-size: 1rem;
line-height: 28px;
}
.toggle-container {
margin: 0 10px;
gap: 6px;
}
.custom-toggle {
width: 36px;
height: 18px;
}
.custom-toggle::before {
width: 14px;
height: 14px;
top: 2px;
left: 2px;
}
.custom-toggle:checked::before {
transform: translateX(18px);
}
.toggle-container label {
font-size: 0.9rem;
}
#filter-form .form-label {
font-size: 1.1rem;
}
}
</style>
</head>
<body>
<div class="toast-container" id="toastContainer"></div>
<div class="fixed-top-bar">
<div class="avatar-dropdown-container">
<div class="avatar-icon" role="button" aria-label="Open user menu">
<span>{{ first_letter }}</span>
</div>
<div class="dropdown-menu">
<a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item">View Profile</a>
<a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item">Order History</a>
<a href="{{ url_for('logout') }}" class="dropdown-item">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" aria-label="Voice search"></i>
<div class="search-suggestions" id="searchSuggestions"></div>
</div>
</div>
<!-- NEW: Updated Filter Form -->
<form method="get" action="/menu" class="text-center mb-4" id="filter-form">
<label class="form-label fw-bold">Filters:</label>
<div class="toggle-container">
<input type="checkbox" id="veg-toggle" name="veg"
{% if veg_filter %}checked{% endif %}
class="custom-toggle" onchange="handleToggle('veg')" aria-label="Filter vegetarian items">
<label for="veg-toggle">Veg</label>
</div>
<div class="toggle-container">
<input type="checkbox" id="category-CustomizedDish" name="custom"
{% if custom_filter %}checked{% endif %}
class="custom-toggle" onchange="handleToggle('custom')" aria-label="Filter customized dishes">
<label for="category-CustomizedDish">Customized Dish</label>
</div>
</form>
<div class="container mt-4">
{% if custom_filter %}
<div id="custom-dish-form" class="mt-4">
<h3>Create Your Custom Dish</h3>
<form method="POST" action="/customdish/generate_custom_dish">
<div class="mb-3">
<label for="custom-dish-name" class="form-label">Dish Name</label>
<input type="text" class="form-control" id="custom-dish-name" name="name" required aria-required="true">
</div>
<div class="mb-3 position-relative">
<label for="custom-dish-description" class="form-label">Dish Description</label>
<textarea class="form-control" id="custom-dish-description" name="description" required aria-required="true"></textarea>
<div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
</div>
<button type="submit" class="btn btn-primary" aria-label="Submit custom dish">Submit Custom Dish</button>
</form>
</div>
{% else %}
<!-- NEW: Enhanced Empty State Handling -->
{% if ordered_menu.items()|length == 0 %}
<p id="no-items-message">No menu items available for this filter. Please try adjusting the filters or contact support.</p>
<div id="client-side-menu" class="row"></div>
{% 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">
<div class="card menu-card" data-item-name="{{ item.Name | default('Unnamed Item') }}" data-item-section="{{ item.Section__c | default(section) }}" data-is-veg="{{ item.IsVeg__c | default('false') | lower }}">
<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';">
<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>
</div>
<div class="d-flex flex-column align-items-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.Image1__c | default('/static/placeholder.jpg') }}"
data-item-section="{{ item.Section__c | default(section) }}"
data-item-category="{{ selected_category }}"
data-item-description="{{ item.Description__c | default('No description') }}"
data-item-image2="{{ item.Image2__c | default(item.Image1__c) }}"
data-is-veg="{{ item.IsVeg__c | default('false') | lower }}">
{% 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') }}', '{{ item.Price__c | default('0.00') }}', '{{ item.Image2__c | default(item.Image1__c) }}', '{{ item.Description__c | default('No description') }}', '{{ item.Section__c | default(section) }}', '{{ selected_category }}')"
aria-label="Customize {{ item.Name | default('Unnamed Item') }}">
ADD
</button>
{% if item.Section__c != 'Apetizer' and item.Section__c != 'Customized dish' %}
<span class="customisable-text">Customisable</span>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
{% if item.Section__c != 'Soft Drinks' %}
<div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}" role="button" aria-label="Toggle details for {{ item.Name | default('Unnamed Item') }}">Show Details</div>
<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</h6>
<p>{{ item.Ingredientsinfo__c | default('Not specified') }}</p>
<h6>Nutritional Info</h6>
<p class="nutritional-info">{{ 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">
<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 id="cart-item-count" class="cart-icon-badge" style="display: none;">0</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 id="modal-addons" class="modal-addons mt-4">
<h5>Customization Options</h5>
<div id="addons-list" class="addons-container addon-loading"></div>
</div>
<div class="mt-4">
<h6>Special Instructions</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="Selected 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 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">
<div class="modal-header">
<h5 class="modal-title" id="softDrinkModalLabel">Select Quantity</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="soft-drink-image" class="img-fluid rounded mb-3 d-block mx-auto" alt="Soft Drink Image">
<div class="text-center mb-3">
<h5 id="soft-drink-name"></h5>
<p id="soft-drink-price"></p>
</div>
<div class="quantity-controls">
<button type="button" class="btn" id="soft-drink-decrease" onclick="updateSoftDrinkQuantity(-1)" aria-label="Decrease quantity by 1">-</button>
<input type="text" class="form-control text-center" id="soft-drink-quantity" value="1" readonly aria-label="Selected quantity">
<button type="button" class="btn" id="soft-drink-increase" onclick="updateSoftDrinkQuantity(1)" aria-label="Increase quantity by 1">+</button>
</div>
</div>
<div class="modal-footer">
<div class="quantity-controls-footer">
<button type="button" class="btn" id="soft-drink-decrease-footer" onclick="updateSoftDrinkQuantity(-1)" aria-label="Decrease quantity by 1">-1</button>
<button type="button" class="btn" id="soft-drink-increase-footer" onclick="updateSoftDrinkQuantity(1)" aria-label="Increase quantity by 1">+1</button>
</div>
<button type="button" class="btn btn-primary" onclick="addSoftDrinkToCart()" aria-label="Add soft drink to cart">Add to Cart</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
// === Global State ===
let isProcessingRequest = false;
let currentSoftDrinkButton = null;
let baseItemPrice = 0;
// === Menu Data ===
const menuItems = [
{% for section, items in ordered_menu.items() %}
{% for item in items %}
{
name: "{{ item.Name | default('Unnamed Item') }}",
section: "{{ item.Section__c | default(section) }}",
isVeg: {{ item.IsVeg__c | default('false') | lower }},
price: "{{ item.Price__c | default('0.00') }}",
image: "{{ item.Image1__c | default('/static/placeholder.jpg') }}",
image2: "{{ item.Image2__c | default(item.Image1__c) }}",
description: "{{ item.Description__c | default('No description') }}",
video: "{{ item.Video1__c | default('/static/placeholder.mp4') }}"
},
{% endfor %}
{% endfor %}
];
// === Ingredients for Custom Dish ===
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)"
];
// === Utility Functions ===
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function showToast(message) {
const toastContainer = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.classList.add('toast');
toast.innerText = message;
toastContainer.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 100);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// === Cart Functions ===
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 updateCartUI(cart) {
if (!Array.isArray(cart)) {
console.error('Invalid cart data:', cart);
showToast('Error updating 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';
}
}
// === Modal Functions ===
function updateModalPrice() {
const selectedAddOns = Array.from(
document.querySelectorAll('#addons-list input[type="checkbox"]:checked')
).map(addon => parseFloat(addon.getAttribute('data-price')) || 0);
const totalAddOnPrice = selectedAddOns.reduce((sum, price) => sum + price, 0);
const totalPrice = baseItemPrice + totalAddOnPrice;
document.getElementById('modal-price').innerText = `$${totalPrice.toFixed(2)}`;
}
function updateSoftDrinkQuantity(delta) {
const quantityInput = document.getElementById('soft-drink-quantity');
let currentQuantity = parseInt(quantityInput.value) || 1;
currentQuantity = Math.max(1, currentQuantity + delta);
quantityInput.value = currentQuantity;
}
function showSoftDrinkModal(button) {
currentSoftDrinkButton = button;
const buttonContainer = button.closest('.button-container');
const itemName = 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';
document.getElementById('soft-drink-image').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;
const itemName = buttonContainer.getAttribute('data-item-name');
const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
const itemImage = buttonContainer.getAttribute('data-item-image');
const section = buttonContainer.getAttribute('data-item-section');
const selectedCategory = buttonContainer.getAttribute('data-item-category');
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: [],
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) {
updateCartUI(data.cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
} else {
console.error('Failed to add item to cart:', data.error);
showToast('Failed to add item to cart');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
showToast('Error adding item to cart');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
});
}
function showItemDetails(name, price, image, description, section, selectedCategory) {
document.getElementById('modal-name').innerText = name;
baseItemPrice = parseFloat(price) || 0;
document.getElementById('modal-price').innerText = `$${baseItemPrice.toFixed(2)}`;
const modalImg = document.getElementById('modal-img');
modalImg.src = image || '/static/placeholder.jpg';
document.getElementById('modal-description').innerText = description || 'No description available.';
document.getElementById('addons-list').innerHTML = '';
document.getElementById('addons-list').classList.add('addon-loading');
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;
let retryCount = 0;
const maxRetries = 2;
function fetchAddons() {
fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
.then(response => response.json())
.then(data => {
const addonsList = document.getElementById('addons-list');
addonsList.classList.remove('addon-loading');
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');
optionsContainer.classList.add('addon-options');
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}"
aria-label="Select ${option} for ${addon.name}">
<label class="form-check-label" for="${optionId}">
${option} ${addon.extra_charge ? `<span class="extra-charge">($${addon.extra_charge_amount.toFixed(2)})</span>` : ''}
</label>
`;
optionsContainer.appendChild(listItem);
});
sectionDiv.appendChild(optionsContainer);
addonsList.appendChild(sectionDiv);
});
const addonSections = addonsList.querySelectorAll('.addon-section');
addonSections.forEach(section => {
const title = section.querySelector('h6');
const options = section.querySelector('.addon-options');
title.addEventListener('click', () => {
section.classList.toggle('collapsed');
options.classList.toggle('collapsed');
});
});
document.querySelectorAll('.addon-option').forEach(checkbox => {
checkbox.addEventListener('change', updateModalPrice);
});
document.querySelectorAll('.addon-option').forEach(checkbox => {
checkbox.addEventListener('change', function () {
const groupName = this.getAttribute('data-group');
const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it a Combo", "Beverages", "Sauces"].includes(groupName);
if (!isMultiSelectGroup && this.checked) {
document.querySelectorAll(`.addon-option[data-group="${groupName}"]`).forEach(otherCheckbox => {
if (otherCheckbox !== this) {
otherCheckbox.checked = false;
}
});
}
});
});
})
.catch(err => {
console.error('Error fetching add-ons:', err);
if (retryCount < maxRetries) {
retryCount++;
setTimeout(fetchAddons, 1000);
} else {
document.getElementById('addons-list').classList.remove('addon-loading');
document.getElementById('addons-list').innerHTML = '<p>Failed to load customization options. Please try again later.</p>';
showToast('Error loading customization options');
}
});
}
fetchAddons();
}
function addToCartFromModal() {
if (isProcessingRequest) return;
isProcessingRequest = true;
const modalSectionEl = document.getElementById('modal-section');
const section = modalSectionEl.getAttribute('data-section');
const selectedCategory = modalSectionEl.getAttribute('data-category');
const itemName = document.getElementById('modal-name').innerText;
const itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', '')) || 0;
const itemImage = document.getElementById('modal-img').src;
const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
const instructions = document.getElementById('modal-instructions').value;
const selectedAddOns = Array.from(
document.querySelectorAll('#addons-list input[type="checkbox"]:checked')
).map(addon => ({
name: addon.getAttribute('data-name'),
price: parseFloat(addon.getAttribute('data-price')) || 0
}));
if (!itemName || isNaN(itemPrice) || !section || !itemImage || quantity < 1) {
console.error('Invalid cart item data:', { itemName, itemPrice, section, itemImage, quantity });
showToast('Invalid item data. Please try again.');
isProcessingRequest = false;
return;
}
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: selectedAddOns,
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) {
updateCartUI(data.cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
modal.hide();
} else {
console.error('Failed to add item to cart:', data.error);
showToast('Failed to add item to cart');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
modal.hide();
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
showToast('Error adding item to cart');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
modal.hide();
})
.finally(() => {
isProcessingRequest = false;
});
}
// NEW: Updated Filter Form Logic
function handleToggle(source) {
const form = document.getElementById("filter-form");
const veg = document.getElementById("veg-toggle");
const custom = document.getElementById("category-CustomizedDish");
if (!form || !veg || !custom) {
console.error("Form or toggle elements not found");
showToast("Error applying filter");
return;
}
if (source === 'veg') {
if (veg.checked) {
custom.checked = false;
}
} else if (source === 'custom') {
if (custom.checked) {
veg.checked = false;
}
}
// NEW: Log query parameters for debugging
const params = new URLSearchParams();
if (veg.checked) params.set('veg', 'on');
if (custom.checked) params.set('custom', 'on');
console.log('Filter form submitting with:', params.toString());
form.submit();
}
// NEW: Client-Side Filtering for Veg
function applyClientSideVegFilter() {
const noItemsMessage = document.getElementById('no-items-message');
const clientSideMenu = document.getElementById('client-side-menu');
if (!noItemsMessage || !clientSideMenu) return;
const vegItems = menuItems.filter(item => item.isVeg === true);
if (vegItems.length > 0) {
noItemsMessage.innerText = 'Showing vegetarian items (client-side)';
clientSideMenu.innerHTML = '';
const sections = [...new Set(vegItems.map(item => item.section))];
sections.forEach(section => {
const sectionItems = vegItems.filter(item => item.section === section);
const sectionDiv = document.createElement('div');
sectionDiv.innerHTML = `<h3>${section}</h3>`;
const rowDiv = document.createElement('div');
rowDiv.classList.add('row');
sectionItems.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.classList.add('col-md-6', 'mb-4');
itemDiv.innerHTML = `
<div class="card menu-card" data-item-name="${item.name}" data-item-section="${item.section}" data-is-veg="${item.isVeg}">
<video
class="card-img-top menu-video"
muted
loop
preload="auto"
data-src="${item.video}"
poster="${item.image}"
width="350"
height="200"
onmouseover="this.play()"
onmouseout="this.pause(); this.currentTime = 0;"
onerror="this.poster='/static/placeholder.jpg';">
<source src="${item.video}" 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}</h5>
<p class="card-text price">$${item.price}</p>
</div>
<div class="d-flex flex-column align-items-center justify-content-center
<div class="button-container"
data-item-name="${item.name}"
data-item-price="${item.price}"
data-item-image="${item.image}"
data-item-section="${item.section}"
data-item-category="Veg"
data-item-description="${item.description}"
data-item-image2="${item.image2}"
data-is-veg="${item.isVeg}">
<button class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#itemModal"
onclick="showItemDetails('${item.name}', '${item.price}', '${item.image2}', '${item.description}', '${item.section}', 'Veg')"
aria-label="Customize ${item.name}">
ADD
</button>
${item.section !== 'Apetizer' && item.section !== 'Customized Dish' ? '<span class="customisable-text">Customisable</span>' : ''}
</div>
</div>
</div>
<div class="toggle-details" data-item-name="${item.name}" role="button" aria-label="Toggle details for ${item.name}">Show Details</div>
<div class="item-details" id="details-${item.name.replace(/ /g, '-')}">
<h6>Description</h6>
<p>${item.description}</p>
<!-- NEW: Placeholder for ingredients and nutritional info -->
<h6>Ingredients</h6>
<p>Not specified</p>
<h6>Nutritional Info</h6>
<p class="nutritional-info">Not available</p>
<h6>Allergens</h6>
<p>None listed</p>
</div>
</div>
`;
rowDiv.appendChild(itemDiv);
});
sectionDiv.appendChild(rowDiv);
clientSideMenu.appendChild(sectionDiv);
});
// NEW: Re-apply Intersection Observers for client-side rendered cards
const newMenuCards = clientSideMenu.querySelectorAll('.menu-card');
const newMenuVideos = clientSideMenu.querySelectorAll('.menu-video');
newMenuCards.forEach(card => cardObserver.observe(card));
newMenuVideos.forEach(video => videoObserver.observe(video));
// NEW: Re-apply toggle details listeners
const newToggleLinks = clientSideMenu.querySelectorAll('.toggle-details');
newToggleLinks.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 = document.querySelector(`.toggle-details[data-item-name="${otherDetails.id.replace('details-', '').replace(/-/g, ' ')}"]`);
if (otherLink) {
otherLink.innerText = 'Show Details';
}
}
});
if (!isCurrentlyShown) {
detailsDiv.classList.add('show');
this.innerText = 'Hide Details';
} else {
detailsDiv.classList.remove('show');
this.innerText = 'Show Details';
}
});
});
} else {
noItemsMessage.innerText = 'No vegetarian items available. Please try adjusting the filters or contact support.';
}
}
// NEW: Apply client-side filter if no items are returned
if (document.getElementById('no-items-message')) {
applyClientSideVegFilter();
}
// === Search Functionality ===
const searchBar = document.getElementById('searchBar');
const searchSuggestions = document.getElementById('searchSuggestions');
const debouncedSearch = debounce(function () {
const query = searchBar.value.trim().toLowerCase();
searchSuggestions.innerHTML = '';
searchSuggestions.style.display = 'none';
if (query.length >= 2) {
const filteredItems = menuItems.filter(item =>
item.name.toLowerCase().includes(query) ||
item.section.toLowerCase().includes(query) ||
item.description.toLowerCase().includes(query)
);
if (filteredItems.length > 0) {
filteredItems.forEach(item => {
const suggestionDiv = document.createElement('div');
suggestionDiv.classList.add('suggestion-item');
suggestionDiv.innerText = item.name;
suggestionDiv.addEventListener('click', () => {
searchBar.value = item.name;
searchSuggestions.style.display = 'none';
window.location.href = '/search';
});
searchSuggestions.appendChild(suggestionDiv);
});
searchSuggestions.style.display = 'block';
}
}
}, 300);
searchBar.addEventListener('input', debouncedSearch);
document.addEventListener('click', function (event) {
if (!searchBar.contains(event.target) && !searchSuggestions.contains(event.target)) {
searchSuggestions.style.display = 'none';
}
});
// === Dropdown Menu ===
const avatarIcon = document.querySelector('.avatar-icon');
const dropdownMenu = document.querySelector('.dropdown-menu');
avatarIcon.addEventListener('click', function () {
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
});
document.addEventListener('click', function (event) {
if (!avatarIcon.contains(event.target) && !dropdownMenu.contains(event.target)) {
dropdownMenu.style.display = 'none';
}
});
// === Intersection Observers ===
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 && src !== '/static/placeholder.mp4') {
const source = video.querySelector('source');
if (source && !source.src) {
source.src = src;
video.load().catch(err => {
console.error('Error loading video:', err);
video.poster = '/static/placeholder.jpg';
});
}
} else {
video.poster = '/static/placeholder.jpg';
}
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));
// === Toggle Details ===
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 = document.querySelector(`.toggle-details[data-item-name="${otherDetails.id.replace('details-', '').replace(/-/g, ' ')}"]`);
if (otherLink) {
otherLink.innerText = 'Show Details';
}
}
});
if (!isCurrentlyShown) {
detailsDiv.classList.add('show');
this.innerText = 'Hide Details';
} else {
detailsDiv.classList.remove('show');
this.innerText = 'Show Details';
}
});
});
// === Custom Dish Form Suggestions ===
const descriptionTextarea = document.getElementById('custom-dish-description');
const descriptionSuggestions = document.getElementById('descriptionSuggestions');
if (descriptionTextarea && descriptionSuggestions) {
let usedIngredients = new Set();
function updateUsedIngredients() {
const inputText = descriptionTextarea.value.trim();
usedIngredients.clear();
if (inputText) {
const words = inputText.split(/,\s*/).map(word => word.trim());
words.forEach(word => {
if (word && ingredientsList.includes(word)) {
usedIngredients.add(word);
}
});
}
}
const debouncedSuggestions = debounce(function () {
const inputText = descriptionTextarea.value.trim();
const words = inputText.split(/,\s*/);
const lastWord = words[words.length - 1].trim().toLowerCase();
descriptionSuggestions.innerHTML = '';
descriptionSuggestions.style.display = 'none';
updateUsedIngredients();
if (lastWord) {
const filteredIngredients = ingredientsList.filter(ingredient =>
ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
);
if (filteredIngredients.length > 0) {
filteredIngredients.forEach(ingredient => {
const suggestionDiv = document.createElement('div');
suggestionDiv.classList.add('suggestion-item');
suggestionDiv.innerText = ingredient;
suggestionDiv.addEventListener('click', function () {
const currentValue = descriptionTextarea.value;
const lastCommaIndex = currentValue.lastIndexOf(',');
const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
descriptionSuggestions.style.display = 'none';
descriptionTextarea.focus();
updateUsedIngredients();
});
descriptionSuggestions.appendChild(suggestionDiv);
});
descriptionSuggestions.style.display = 'block';
}
}
}, 300);
descriptionTextarea.addEventListener('input', debouncedSuggestions);
document.addEventListener('click', function (event) {
if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
descriptionSuggestions.style.display = 'none';
}
});
}
// === Fetch Initial Cart ===
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);
showToast('Failed to load cart');
const cart = getCartLocalStorage();
updateCartUI(cart);
}
})
.catch(err => {
console.error('Error fetching cart:', err);
showToast('Error loading cart');
const cart = getCartLocalStorage();
updateCartUI(cart);
});
// === Quantity Controls for Item Modal ===
const decreaseBtn = document.getElementById('decreaseQuantity');
const increaseBtn = document.getElementById('increaseQuantity');
const quantityInput = document.getElementById('quantityInput');
decreaseBtn.addEventListener('click', function () {
let quantity = parseInt(quantityInput.value) || 1;
quantity = Math.max(1, quantity - 1);
quantityInput.value = quantity;
});
increaseBtn.addEventListener('click', function () {
let quantity = parseInt(quantityInput.value) || 1;
quantity += 1;
quantityInput.value = quantity;
});
// === Keyboard Accessibility for Search Bar ===
searchBar.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && searchBar.value.trim()) {
window.location.href = '/search';
}
});
});
</script>
</body>
</html>