lokesh341 commited on
Commit
9cb50d5
·
verified ·
1 Parent(s): 0d04669

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. templates/menu.html +828 -468
templates/menu.html CHANGED
@@ -7,26 +7,42 @@
7
  <!-- Bootstrap CSS -->
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
 
10
  <!-- Preload Critical Resources -->
11
- <link rel="preload" href="/static/placeholder.mp4" as="video">
12
  {% for section, items in ordered_menu.items() %}
13
  {% for item in items[:1] %}
14
- <link rel="preload" href="{{ item.Video1__c | default('/static/placeholder.mp4') }}" as="video" fetchpriority="high">
15
  {% endfor %}
16
  {% endfor %}
 
17
  <style>
 
 
 
 
 
 
 
 
 
 
 
18
  body {
19
- font-family: Arial, sans-serif;
20
- background-color: #fdf4e3;
21
  margin: 0;
22
  padding: 0;
23
  display: flex;
24
  flex-direction: column;
25
  padding-bottom: 70px;
 
26
  }
 
27
  .container {
28
  max-width: 900px;
29
  }
 
30
  .menu-card {
31
  max-width: 350px;
32
  border-radius: 15px;
@@ -37,55 +53,70 @@
37
  flex-direction: column;
38
  opacity: 0;
39
  transition: opacity 0.3s ease-in-out;
40
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
41
  }
 
42
  .menu-card.visible {
43
  opacity: 1;
44
  }
 
45
  .video-container {
46
- transition: all 0.3s ease;
47
  position: relative;
48
- }
49
- .video-container.video-error .video-fallback {
50
- opacity: 1 !important;
51
- }
52
- .video-container.video-error video {
53
- display: none !important;
54
- }
55
- .video-container:focus-within {
56
- outline: 2px solid #3a86ff;
57
- outline-offset: 2px;
58
- }
59
- .menu-video {
60
  height: 200px;
 
 
 
 
 
 
 
61
  width: 100%;
 
62
  object-fit: cover;
63
- border-radius: 15px 15px 0 0;
64
  opacity: 0;
65
  transition: opacity 0.5s ease-in-out;
66
- background-color: #000;
67
  }
68
- .menu-video.loaded {
 
69
  opacity: 1;
70
  }
71
- .menu-card:hover .menu-video {
 
 
 
 
 
 
 
 
 
 
 
 
72
  opacity: 1;
73
- transform: scale(1.05);
74
  }
 
 
 
 
 
75
  .menu-card .card-body .card-title {
76
  font-size: 1.2rem;
77
  font-weight: 600;
78
  margin: 10px 0;
79
- color: #333333;
80
  }
 
81
  .menu-card .card-body .card-text.price {
82
  font-size: 1rem;
83
  font-weight: 500;
84
  color: #000000;
85
  margin-bottom: 5px;
86
  }
 
87
  .addbutton .btn {
88
- background-color: #28a745;
89
  color: white;
90
  padding: 10px 20px;
91
  font-size: 16px;
@@ -95,9 +126,11 @@
95
  transition: background-color 0.3s ease;
96
  margin-left: 13px;
97
  }
 
98
  .addbutton .btn:hover {
99
- background-color: #218838;
100
  }
 
101
  .button-container {
102
  display: flex;
103
  flex-direction: column;
@@ -105,40 +138,44 @@
105
  justify-content: center;
106
  gap: 6px;
107
  }
 
108
  .customisable-text {
109
- color: #0FAA39;
110
  font-size: 10px;
111
  font-weight: 500;
112
  margin: 0;
113
  text-align: center;
114
  line-height: 1;
115
  }
 
116
  .btn-primary {
117
  font-size: 12px;
118
  font-weight: bold;
119
  border-radius: 8px;
120
  width: 70px;
121
  height: 35px;
122
- background-color: #0FAA39;
123
- border-color: #0FAA39;
124
  display: flex;
125
  align-items: center;
126
  justify-content: center;
127
  padding: 0;
128
  transition: background-color 0.3s ease, transform 0.1s ease;
129
  }
 
130
  .btn-primary:hover {
131
- background-color: #0D9232;
132
- border-color: #0D9232;
133
- transform: scale(1.05);
134
  }
 
135
  .btn-primary:active,
136
  .btn-primary:focus {
137
- background-color: #0B7A29;
138
- border-color: #0B7A29;
139
  box-shadow: none;
140
  transform: scale(0.98);
141
  }
 
142
  .avatar-dropdown-container {
143
  position: absolute;
144
  right: 10px;
@@ -148,6 +185,7 @@
148
  align-items: right;
149
  justify-content: center;
150
  }
 
151
  .avatar-icon {
152
  width: 40px;
153
  height: 40px;
@@ -161,6 +199,7 @@
161
  font-size: 20px;
162
  font-weight: bold;
163
  }
 
164
  .dropdown-menu {
165
  position: absolute;
166
  right: 0;
@@ -172,6 +211,7 @@
172
  display: none;
173
  border: 1px solid #ffd8b1;
174
  }
 
175
  .dropdown-menu .dropdown-item {
176
  padding: 12px 16px;
177
  text-decoration: none;
@@ -181,20 +221,23 @@
181
  font-size: 15px;
182
  transition: background-color 0.2s ease;
183
  }
 
184
  .dropdown-menu .dropdown-item:last-child {
185
  border-bottom: none;
186
  }
 
187
  .dropdown-menu .dropdown-item:hover {
188
  background-color: #ffe4c4;
189
  color: #333;
190
  }
 
191
  .fixed-top-bar {
192
  position: relative;
193
  top: 0;
194
  left: 0;
195
  width: 100%;
196
  height: 54px;
197
- background: linear-gradient(45deg, #FFA07A, #FFB347);
198
  color: white;
199
  padding: 15px;
200
  display: flex;
@@ -202,6 +245,7 @@
202
  align-items: center;
203
  z-index: 1000;
204
  }
 
205
  .search-bar-container {
206
  position: absolute;
207
  left: 20px;
@@ -213,6 +257,7 @@
213
  max-width: 90%;
214
  position: relative;
215
  }
 
216
  .search-bar-container input {
217
  width: 100%;
218
  padding: 8px 40px 8px 40px;
@@ -223,15 +268,18 @@
223
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
224
  outline: none;
225
  }
 
226
  .search-bar-container input::placeholder {
227
  color: #888;
228
  }
 
229
  .search-icon {
230
  position: absolute;
231
  left: 15px;
232
  font-size: 18px;
233
  color: #888;
234
  }
 
235
  .mic-icon {
236
  position: absolute;
237
  right: 15px;
@@ -240,9 +288,11 @@
240
  cursor: pointer;
241
  transition: color 0.3s ease;
242
  }
 
243
  .mic-icon.active {
244
  color: #007bff;
245
  }
 
246
  .autocomplete-suggestions {
247
  position: absolute;
248
  top: 100%;
@@ -257,15 +307,18 @@
257
  z-index: 1000;
258
  display: none;
259
  }
 
260
  .autocomplete-suggestions .suggestion-item {
261
  padding: 8px 15px;
262
  cursor: pointer;
263
  font-size: 14px;
264
  color: #333;
265
  }
 
266
  .autocomplete-suggestions .suggestion-item:hover {
267
  background-color: #f1f1f1;
268
  }
 
269
  .addon-section {
270
  background-color: #fff;
271
  border: 2px solid #ffa500;
@@ -273,18 +326,21 @@
273
  padding: 12px;
274
  margin-bottom: 10px;
275
  }
 
276
  .addon-section h6 {
277
  margin-bottom: 10px;
278
  font-size: 1.1rem;
279
  font-weight: bold;
280
  color: #343a40;
281
  }
 
282
  .addon-section .form-check {
283
  display: inline-flex;
284
  align-items: center;
285
  margin-left: 10px;
286
  color: #343a40;
287
  }
 
288
  .addon-section .form-check-input {
289
  -webkit-appearance: none;
290
  -moz-appearance: none;
@@ -297,10 +353,12 @@
297
  position: relative;
298
  margin-right: 10px;
299
  }
 
300
  .addon-section .form-check-input:checked {
301
  background-color: #006400;
302
  border-color: #006400;
303
  }
 
304
  .addon-section .form-check-input:checked::before {
305
  content: '\2713';
306
  font-size: 14px;
@@ -309,6 +367,7 @@
309
  left: 4px;
310
  color: white;
311
  }
 
312
  .addon-section .form-check-label {
313
  font-size: 16px;
314
  margin-left: 5px;
@@ -317,6 +376,7 @@
317
  display: inline-block;
318
  vertical-align: middle;
319
  }
 
320
  form.text-center.mb-4 {
321
  display: flex;
322
  flex-direction: column;
@@ -324,18 +384,22 @@
324
  justify-content: center;
325
  margin-bottom: 5px;
326
  }
 
327
  .modal-header {
328
  padding: 10px 15px;
329
  }
 
330
  .modal-title {
331
  font-size: 16px;
332
  font-weight: bold;
333
  }
 
334
  .modal-body {
335
  max-height: 60vh;
336
  overflow-y: auto;
337
  padding: 15px;
338
  }
 
339
  .modal-body #modal-img {
340
  max-height: 200px;
341
  width: 100%;
@@ -343,6 +407,7 @@
343
  border-radius: 8px;
344
  margin-bottom: 10px;
345
  }
 
346
  .modal-body #modal-name {
347
  font-size: 20px;
348
  font-weight: bold;
@@ -350,6 +415,7 @@
350
  margin-bottom: 5px;
351
  color: #333333;
352
  }
 
353
  .modal-body #modal-price {
354
  font-size: 16px;
355
  font-weight: 500;
@@ -357,45 +423,53 @@
357
  text-align: center;
358
  margin-bottom: 10px;
359
  }
 
360
  .modal-body #modal-description {
361
  font-size: 14px;
362
  color: #6c757d;
363
  margin-bottom: 10px;
364
  }
 
365
  .modal-body .nutritional-info {
366
  font-size: 12px;
367
  color: #6c757d;
368
  margin-bottom: 10px;
369
  }
 
370
  .modal-body #modal-addons h6,
371
  .modal-body #first-row h6 {
372
  font-size: 14px;
373
  font-weight: bold;
374
  margin-bottom: 10px;
375
  }
 
376
  .modal-footer {
377
  display: flex;
378
  align-items: center;
379
  justify-content: space-between;
380
  padding: 10px;
381
  }
 
382
  .modal-footer .d-flex {
383
  display: flex;
384
  align-items: center;
385
  gap: 10px;
386
  }
 
387
  .modal-footer .btn {
388
  height: 40px;
389
  padding: 0 15px;
390
  }
 
391
  .modal-footer .form-control {
392
  width: 50px;
393
  height: 40px;
394
  text-align: center;
395
  }
 
396
  .modal-footer .btn-primary {
397
- background-color: #0FAA39;
398
- border-color: #0FAA39;
399
  font-weight: bold;
400
  padding: 10px 20px;
401
  height: 40px;
@@ -404,10 +478,12 @@
404
  align-items: center;
405
  width: auto;
406
  }
 
407
  .modal-footer .btn-outline-secondary {
408
  height: 40px;
409
  width: 40px;
410
  }
 
411
  .item-details {
412
  background-color: #f8f9fa;
413
  border-radius: 8px;
@@ -415,20 +491,24 @@
415
  margin: 10px 15px;
416
  display: none;
417
  }
 
418
  .item-details.show {
419
  display: block;
420
  }
 
421
  .item-details h6 {
422
  font-size: 0.9rem;
423
  font-weight: bold;
424
  margin-bottom: 5px;
425
  color: #333333;
426
  }
 
427
  .item-details p {
428
  font-size: 0.85rem;
429
  margin-bottom: 5px;
430
  color: #333;
431
  }
 
432
  .item-details .nutritional-info {
433
  display: grid;
434
  grid-template-columns: 1fr 1fr;
@@ -439,17 +519,20 @@
439
  font-size: 0.85rem;
440
  line-height: 1.5;
441
  }
 
442
  .toggle-details {
443
  cursor: pointer;
444
- color: #0FAA39;
445
  font-size: 0.9rem;
446
  margin-left: 15px;
447
  margin-bottom: 10px;
448
  display: inline-block;
449
  }
 
450
  .toggle-details:hover {
451
  text-decoration: underline;
452
  }
 
453
  .category-buttons {
454
  display: flex;
455
  flex-wrap: wrap;
@@ -457,29 +540,34 @@
457
  justify-content: center;
458
  margin-top: 10px;
459
  }
 
460
  .category-button {
461
  background-color: #fff;
462
- border: 2px solid #0FAA39;
463
- color: #0FAA39;
464
  padding: 5px 15px;
465
  border-radius: 20px;
466
  font-size: 0.9rem;
467
  cursor: pointer;
468
  transition: background-color 0.3s, color 0.3s;
469
  }
 
470
  .category-button.selected {
471
- background-color: #0FAA39;
472
  color: #fff;
473
- border-color: #0FAA39;
474
  }
 
475
  .category-button:hover {
476
  background-color: #e6f4ea;
477
  }
 
478
  .quantity-selector {
479
  display: flex;
480
  align-items: center;
481
  gap: 5px;
482
  }
 
483
  .quantity-selector .btn {
484
  width: 25px;
485
  height: 25px;
@@ -488,6 +576,7 @@
488
  line-height: 25px;
489
  text-align: center;
490
  }
 
491
  .quantity-selector .quantity-display {
492
  width: 25px;
493
  text-align: center;
@@ -495,6 +584,7 @@
495
  font-weight: bold;
496
  line-height: 25px;
497
  }
 
498
  .quantity-selector .quantity-to-add,
499
  .quantity-selector .quantity-to-remove {
500
  width: 45px;
@@ -502,27 +592,34 @@
502
  font-size: 12px;
503
  padding: 0 5px;
504
  }
 
505
  .modal-dialog {
506
  max-height: 90vh;
507
  }
 
508
  .modal-body::-webkit-scrollbar {
509
  width: 8px;
510
  }
 
511
  .modal-body::-webkit-scrollbar-track {
512
  background: #f1f1f1;
513
  border-radius: 10px;
514
  }
 
515
  .modal-body::-webkit-scrollbar-thumb {
516
- background: #0FAA39;
517
  border-radius: 10px;
518
  }
 
519
  .modal-body::-webkit-scrollbar-thumb:hover {
520
- background: #0D9232;
521
  }
 
522
  .btn-primary:disabled {
523
  opacity: 0.65;
524
  cursor: not-allowed;
525
  }
 
526
  .quantity-selector select {
527
  width: 60px;
528
  height: 35px;
@@ -530,10 +627,12 @@
530
  border-radius: 5px;
531
  border: 1px solid #ced4da;
532
  }
 
533
  #custom-dish-form {
534
  position: relative;
535
  padding-bottom: 80px;
536
  }
 
537
  #custom-dish-form .btn-primary {
538
  position: absolute;
539
  right: 15px;
@@ -541,6 +640,7 @@
541
  width: auto;
542
  padding: 10px 20px;
543
  }
 
544
  .bottom-action-bar {
545
  position: fixed;
546
  bottom: 0;
@@ -556,6 +656,7 @@
556
  max-width: 900px;
557
  margin: 0 auto;
558
  }
 
559
  .bottom-action-bar .btn {
560
  flex: 1;
561
  margin: 0 5px;
@@ -571,25 +672,30 @@
571
  min-width: 0;
572
  white-space: nowrap;
573
  }
 
574
  .bottom-action-bar .btn-order-history {
575
- background-color: #FFA07A;
576
- border-color: #FFA07A;
577
  }
 
578
  .bottom-action-bar .btn-order-history:hover {
579
- background-color: #FF8C61;
580
- border-color: #FF8C61;
581
  }
 
582
  .bottom-action-bar .btn-view-cart {
583
- background-color: #0FAA39;
584
- border-color: #0FAA39;
585
  }
 
586
  .bottom-action-bar .btn-view-cart:hover {
587
- background-color: #0D9232;
588
- border-color: #0D9232;
589
  }
 
590
  .cart-icon-badge {
591
  background-color: white;
592
- color: #0FAA39;
593
  border-radius: 50%;
594
  width: 20px;
595
  height: 20px;
@@ -599,11 +705,68 @@
599
  font-size: 12px;
600
  margin-left: 8px;
601
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  @media (max-width: 576px) {
603
  .fixed-top-bar {
604
  height: 60px;
605
  padding: 10px;
606
  }
 
607
  .search-bar-container {
608
  width: 80%;
609
  max-width: 100%;
@@ -611,55 +774,69 @@
611
  top: 50%;
612
  transform: translateY(-50%);
613
  }
 
614
  .search-bar-container input {
615
  padding: 6px 35px 6px 35px;
616
  font-size: 14px;
617
  border-radius: 20px;
618
  }
 
619
  .search-icon {
620
  left: 12px;
621
  font-size: 16px;
622
  }
 
623
  .mic-icon {
624
  right: 12px;
625
  font-size: 16px;
626
  }
 
627
  .avatar-dropdown-container {
628
  right: 10px;
629
  }
 
630
  .avatar-icon {
631
  width: 40px;
632
  height: 40px;
633
  font-size: 20px;
634
  }
 
635
  .dropdown-menu {
636
  width: 220px;
637
  }
 
638
  .dropdown-menu .dropdown-item {
639
  padding: 12px 16px;
640
  font-size: 15px;
641
  }
 
642
  .category-buttons {
643
  gap: 8px;
644
  }
 
645
  .category-button {
646
  padding: 4px 12px;
647
  font-size: 0.85rem;
648
  }
 
649
  .modal-dialog {
650
  max-width: 96%;
651
  margin: 5px auto;
652
  }
 
653
  .modal-header {
654
  padding: 5px 10px;
655
  }
 
656
  .modal-title {
657
  font-size: 14px;
658
  }
 
659
  .modal-body {
660
  max-height: 50vh;
661
  padding: 8px;
662
  }
 
663
  .modal-body #modal-img {
664
  max-height: 150px;
665
  width: 100%;
@@ -667,92 +844,112 @@
667
  margin: 0 auto 5px;
668
  display: block;
669
  }
 
670
  .modal-body #modal-name {
671
  font-size: 18px;
672
  margin-bottom: 3px;
673
  }
 
674
  .modal-body #modal-price {
675
  font-size: 14px;
676
  margin-bottom: 5px;
677
  }
 
678
  .modal-body #modal-description {
679
  font-size: 12px;
680
  margin-bottom: 5px;
681
  }
 
682
  .modal-body .nutritional-info {
683
  font-size: 10px;
684
  margin-bottom: 5px;
685
  }
 
686
  .modal-body #modal-addons h6,
687
  .modal-body #first-row h6 {
688
  font-size: 12px;
689
  margin-bottom: 5px;
690
  }
 
691
  .modal-footer {
692
  padding: 5px;
693
  }
 
694
  .modal-footer .btn {
695
  height: 30px;
696
  padding: 0 10px;
697
  }
 
698
  .modal-footer .form-control {
699
  width: 30px;
700
  height: 30px;
701
  font-size: 12px;
702
  font-weight: bold;
703
  }
 
704
  .modal-footer .btn-outline-secondary {
705
  width: 25px;
706
  height: 25px;
707
  font-size: 12px;
708
  line-height: 25px;
709
  }
 
710
  .modal-footer .btn-primary {
711
  font-size: 12px;
712
  height: 30px;
713
  padding: 0 15px;
714
  border-radius: 5px;
715
  }
 
716
  .btn-primary {
717
  font-size: 10px;
718
  width: 50px;
719
  height: 25px;
720
  }
 
721
  .customisable-text {
722
  font-size: 8px;
723
  }
 
724
  .button-container {
725
  gap: 3px;
726
  }
 
727
  .quantity-selector .btn {
728
  width: 18px;
729
  height: 18px;
730
  font-size: 9px;
731
  line-height: 18px;
732
  }
 
733
  .quantity-selector .quantity-display {
734
  width: 18px;
735
  font-size: 9px;
736
  line-height: 18px;
737
  }
 
738
  .quantity-selector .quantity-to-add,
739
  .quantity-selector .quantity-to-remove {
740
  width: 35px;
741
  height: 18px;
742
  font-size: 9px;
743
  }
 
744
  .quantity-selector select {
745
  width: 50px;
746
  height: 30px;
747
  font-size: 12px;
748
  }
 
749
  .bottom-action-bar {
750
  padding: 8px 10px;
751
  }
 
752
  .bottom-action-bar .btn {
753
  padding: 8px 10px;
754
  font-size: 14px;
755
  }
 
756
  .cart-icon-badge {
757
  width: 18px;
758
  height: 18px;
@@ -763,32 +960,43 @@
763
  </style>
764
  </head>
765
  <body>
 
 
 
 
766
  <div class="fixed-top-bar">
767
  <div class="avatar-dropdown-container">
768
- <div class="avatar-icon">
769
  <span>{{ first_letter }}</span>
770
  </div>
771
- <div class="dropdown-menu">
772
- <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item">View Profile</a>
773
- <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item">Order History</a>
774
- <a href="{{ url_for('logout') }}" class="dropdown-item">Logout</a>
775
  </div>
776
  </div>
777
  <div class="search-bar-container">
778
- <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off">
 
779
  <i class="bi bi-search search-icon"></i>
780
- <i class="bi bi-mic mic-icon" id="micIcon"></i>
781
- <div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
782
  </div>
783
  </div>
784
 
785
  <form method="get" action="/menu" class="text-center mb-4" id="categoryForm">
786
- <label class="form-label fw-bold">Select a Category:</label>
787
  <div class="category-buttons">
788
  {% for category in categories %}
789
- <button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}">{{ category }}</button>
 
 
 
790
  {% endfor %}
791
- <button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish">Customized Dish</button>
 
 
 
792
  </div>
793
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
794
  </form>
@@ -796,79 +1004,89 @@
796
  <div class="container mt-4">
797
  {% if selected_category == "Customized Dish" %}
798
  <div id="custom-dish-form" class="mt-4">
799
- <h3>Create Your Custom Dish</h3>
800
  <form method="POST" action="/customdish/generate_custom_dish">
801
  <div class="mb-3">
802
  <label for="custom-dish-name" class="form-label">Dish Name</label>
803
- <input type="text" class="form-control" id="custom-dish-name" name="name" required>
 
 
804
  </div>
805
  <div class="mb-3 position-relative">
806
  <label for="custom-dish-description" class="form-label">Dish Description</label>
807
- <textarea class="form-control" id="custom-dish-description" name="description" required></textarea>
808
- <div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
 
 
809
  </div>
810
  <button type="submit" class="btn btn-primary">Submit Custom Dish</button>
811
  </form>
812
  </div>
813
  {% else %}
814
  {% if ordered_menu.items()|length == 0 %}
815
- <p>No menu items available for this category.</p>
816
  {% else %}
817
  {% for section, items in ordered_menu.items() %}
818
- <h3>{{ section }}</h3>
819
- <div class="row">
820
  {% for item in items %}
821
  <div class="col-md-6 mb-4">
822
  <div class="card menu-card">
823
- <div class="video-container">
824
- <video
825
- class="card-img-top menu-video"
826
- id="video-{{ loop.index }}"
827
- muted
828
- loop
829
- preload="metadata"
830
- autoplay
831
- data-src="{{ item.Video1__c | default('/static/placeholder.mp4') }}"
832
- poster="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
833
- width="350"
834
- height="200"
835
- aria-label="Video demonstration of {{ item.Name }}"
836
- onmouseover="handleVideoHover(this.parentElement, true)"
837
- onmouseout="handleVideoHover(this.parentElement, false)"
838
- onerror="this.parentElement.classList.add('video-error');">
839
- <source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
840
- Your browser does not support the video tag.
841
  </video>
842
  <img class="video-fallback"
843
- src="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
844
- alt="{{ item.Name }}"
845
- style="opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: opacity 0.3s ease;">
846
  </div>
847
  <div class="addbutton">
848
  <div class="card-body d-flex align-items-center justify-content-between">
849
  <div>
850
- <h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
851
  <p class="card-text">${{ item.Price__c | default('0.00') }}</p>
852
  </div>
853
  <div class="d-flex flex-column align-item-center justify-content-center">
854
  <div class="button-container"
855
  data-item-name="{{ item.Name | default('Unnamed Item') }}"
856
- data-item-price="{{ item.Price transcriptase__c | default('0.00') }}"
857
  data-item-image="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
858
  data-item-section="{{ item.Section__c | default(section) }}"
859
  data-item-category="{{ selected_category }}">
860
  {% if item.Section__c == 'Soft Drinks' %}
861
- <button class="btn btn-primary add-to-cart-btn" onclick="handleSoftDrinkAdd(this)">ADD</button>
 
 
 
 
862
  <div class="quantity-selector" style="display: none;">
863
- <button class="btn btn-outline-secondary decrease-btn" onclick="decreaseQuantity(this)">-</button>
864
- <span class="quantity-display">0</span>
865
- <button class="btn btn-outline-secondary increase-btn" onclick="increaseQuantity(this)">+</button>
 
 
 
 
866
  </div>
867
  {% else %}
868
  <button class="btn btn-primary"
869
  data-bs-toggle="modal"
870
  data-bs-target="#itemModal"
871
- 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 }}')">
 
872
  ADD
873
  </button>
874
  {% endif %}
@@ -879,8 +1097,15 @@
879
  </div>
880
  </div>
881
  </div>
882
- <div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}">Show Details</div>
883
- <div class="item-details" id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}"></div>
 
 
 
 
 
 
 
884
  </div>
885
  </div>
886
  {% endfor %}
@@ -891,45 +1116,59 @@
891
  </div>
892
 
893
  <div class="bottom-action-bar">
894
- <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history">
895
- <i class="bi bi-clock-history"></i> Order History
896
  </a>
897
- <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart">
898
- <i class="bi bi-cart"></i> View Cart
899
- <span class="cart-icon-badge" id="cart-item-count">0</span>
900
  </a>
901
  </div>
902
 
903
  <!-- Modal for Item Details -->
904
- <div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
905
- <div class="modal-dialog modal-dialog-centered">
906
  <div class="modal-content">
907
  <div class="modal-header">
908
- <h5 class="modal-title" id="itemModalLabel">Item Details</h5>
909
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
910
  </div>
911
  <div class="modal-body">
912
- <img id="modal-img" class="img-fluid rounded mb-3 d-block mx-auto" alt="Item Image" style="max-height: 200px; object-fit: cover;">
913
- <h5 id="modal-name" class="fw-bold text-center"></h5>
914
- <p id="modal-price" class="text-muted text-center"></p>
 
 
 
915
  <p id="modal-description" class="text-secondary"></p>
916
- <div id="modal-addons" class="modal-addons mt-4">
917
- <h6>Customization Options</h6>
 
918
  <div id="addons-list" class="addons-container">Loading customization options...</div>
919
  </div>
920
- <div class="mt-4">
921
- <h6>Custom Request</h6>
922
- <textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..."></textarea>
 
 
923
  </div>
 
924
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
925
  </div>
 
926
  <div class="modal-footer d-flex align-items-center justify-content-between">
927
  <div class="d-flex align-items-center gap-2">
928
- <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity">-</button>
929
- <input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;"/>
930
- <button type="button" class="btn btn-outline-secondary" id="increaseQuantity">+</button>
 
 
931
  </div>
932
- <button type="button" class="btn btn-primary" onclick="addToCartFromModal()">Add to Cart</button>
 
 
 
 
933
  </div>
934
  </div>
935
  </div>
@@ -937,8 +1176,7 @@
937
 
938
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
939
  <script>
940
- let isProcessingRequest = false;
941
-
942
  const menuItems = [
943
  {% for section, items in ordered_menu.items() %}
944
  {% for item in items %}
@@ -1056,49 +1294,92 @@
1056
  }
1057
  };
1058
 
1059
- function getValidVideoUrl(videoUrl) {
1060
- if (!videoUrl) return null;
1061
- const sfDomain = window.SF_DOMAIN || 'https://yourdomain.my.salesforce.com';
1062
- if (videoUrl.startsWith('http')) return videoUrl;
1063
- if (videoUrl.startsWith('/')) return videoUrl;
1064
- if (videoUrl.startsWith('069')) return `${sfDomain}/sfc/servlet.shepherd/version/download/${videoUrl}`;
1065
- return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
  }
1067
 
1068
- async function handleVideoHover(container, isHovering) {
1069
  const video = container.querySelector('video');
1070
- if (!video) return;
1071
 
1072
  try {
1073
- if (isHovering) {
1074
- if (!video.src && video.dataset.src) {
1075
- video.src = getValidVideoUrl(video.dataset.src);
1076
- await video.load();
1077
- }
1078
- try {
1079
- await video.play();
1080
- video.classList.add('visible');
1081
- if (video.controls !== true) video.controls = true;
1082
- } catch (e) {
1083
- console.warn('Autoplay blocked:', e);
1084
- video.controls = true;
1085
- }
1086
  } else {
1087
  video.pause();
1088
- video.currentTime = 0;
1089
- video.controls = false;
1090
- video.classList.remove('visible');
1091
  }
1092
  } catch (e) {
1093
- console.error('Video handling error:', e);
1094
- const fallbackImg = container.querySelector('.video-fallback');
1095
- if (fallbackImg) {
1096
- video.style.display = 'none';
1097
- fallbackImg.style.opacity = '1';
1098
- }
1099
  }
1100
  }
1101
 
 
 
 
 
 
 
 
 
 
1102
  function addToCartLocalStorage(payload) {
1103
  let cart = JSON.parse(localStorage.getItem('cart')) || [];
1104
  const existingItem = cart.find(item =>
@@ -1106,11 +1387,13 @@
1106
  item.instructions === payload.instructions &&
1107
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
1108
  );
 
1109
  if (existingItem) {
1110
- existingItem.quantity = payload.quantity;
1111
  } else {
1112
  cart.push(payload);
1113
  }
 
1114
  localStorage.setItem('cart', JSON.stringify(cart));
1115
  return cart;
1116
  }
@@ -1122,6 +1405,7 @@
1122
  item.instructions === instructions &&
1123
  JSON.stringify(item.addons) === JSON.stringify(addons)
1124
  );
 
1125
  if (itemIndex !== -1) {
1126
  if (quantityToRemove >= cart[itemIndex].quantity) {
1127
  cart.splice(itemIndex, 1);
@@ -1129,6 +1413,7 @@
1129
  cart[itemIndex].quantity -= quantityToRemove;
1130
  }
1131
  }
 
1132
  localStorage.setItem('cart', JSON.stringify(cart));
1133
  return cart;
1134
  }
@@ -1137,14 +1422,116 @@
1137
  return JSON.parse(localStorage.getItem('cart')) || [];
1138
  }
1139
 
1140
- function debounce(func, wait) {
1141
- let timeout;
1142
- return function (...args) {
1143
- clearTimeout(timeout);
1144
- timeout = setTimeout(() => func.apply(this, args), wait);
1145
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1146
  }
1147
 
 
1148
  function handleSoftDrinkAdd(button) {
1149
  const buttonContainer = button.closest('.button-container');
1150
  const quantitySelector = buttonContainer.querySelector('.quantity-selector');
@@ -1161,9 +1548,6 @@
1161
  }
1162
 
1163
  function increaseQuantity(button) {
1164
- if (isProcessingRequest) return;
1165
- isProcessingRequest = true;
1166
-
1167
  const buttonContainer = button.closest('.button-container');
1168
  const quantityDisplay = buttonContainer.querySelector('.quantity-display');
1169
  let currentQuantity = parseInt(quantityDisplay.innerText) || 0;
@@ -1171,17 +1555,13 @@
1171
  if (currentQuantity < 10) {
1172
  const newQuantity = currentQuantity + 1;
1173
  quantityDisplay.innerText = newQuantity;
1174
- debouncedUpdateCart(buttonContainer, newQuantity);
1175
  } else {
1176
- console.log('Maximum quantity of 10 reached for Soft Drinks');
1177
- isProcessingRequest = false;
1178
  }
1179
  }
1180
 
1181
  function decreaseQuantity(button) {
1182
- if (isProcessingRequest) return;
1183
- isProcessingRequest = true;
1184
-
1185
  const buttonContainer = button.closest('.button-container');
1186
  const quantityDisplay = buttonContainer.querySelector('.quantity-display');
1187
  const addButton = buttonContainer.querySelector('.add-to-cart-btn');
@@ -1196,7 +1576,7 @@
1196
  } else {
1197
  const newQuantity = currentQuantity - 1;
1198
  quantityDisplay.innerText = newQuantity;
1199
- debouncedUpdateCart(buttonContainer, newQuantity);
1200
  }
1201
  }
1202
 
@@ -1219,128 +1599,222 @@
1219
  };
1220
 
1221
  if (newQuantity > 0) {
1222
- fetch('/cart/add', {
 
1223
  method: 'POST',
1224
- headers: {
1225
- 'Content-Type': 'application/json',
1226
- },
1227
- body: JSON.stringify(cartPayload)
1228
- })
1229
- .then(response => response.json())
1230
- .then(data => {
1231
- if (data.success) {
1232
- updateCartUI(data.cart);
1233
- } else {
1234
- console.error('Failed to update cart:', data.error);
1235
- const cart = addToCartLocalStorage(cartPayload);
1236
- updateCartUI(cart);
1237
- }
1238
- })
1239
- .catch(err => {
1240
- console.error('Error updating cart:', err);
1241
- const cart = addToCartLocalStorage(cartPayload);
1242
- updateCartUI(cart);
1243
- })
1244
- .finally(() => {
1245
- isProcessingRequest = false;
1246
  });
1247
  } else {
1248
- fetch(`/cart/remove?item_name=${encodeURIComponent(itemName)}&instructions=&addons=[]`, {
1249
- method: 'POST',
1250
- headers: {
1251
- 'Content-Type': 'application/json',
1252
- }
1253
- })
1254
- .then(response => response.json())
1255
- .then(data => {
1256
- if (data.success) {
1257
- updateCartUI(data.cart);
1258
- } else {
1259
- console.error('Failed to remove item:', data.error);
1260
- const cart = removeFromCartLocalStorage(itemName, 1, '', []);
1261
- updateCartUI(cart);
1262
- }
1263
- })
1264
- .catch(err => {
1265
- console.error('Error removing item:', err);
1266
- const cart = removeFromCartLocalStorage(itemName, 1, '', []);
1267
- updateCartUI(cart);
1268
- })
1269
- .finally(() => {
1270
- isProcessingRequest = false;
1271
  });
1272
  }
1273
  }
1274
 
1275
- const debouncedUpdateCart = debounce(updateCartQuantity, 300);
1276
-
1277
- function updateCartUI(cart) {
1278
- if (!Array.isArray(cart)) {
1279
- console.error('Invalid cart data:', cart);
1280
- return;
1281
- }
1282
-
1283
- let totalQuantity = 0;
1284
- cart.forEach(item => {
1285
- totalQuantity += item.quantity;
1286
- });
1287
-
1288
- const cartItemCount = document.getElementById('cart-item-count');
1289
- if (cartItemCount) {
1290
- cartItemCount.innerText = totalQuantity;
1291
- cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
1292
- }
1293
-
1294
- const buttonContainers = document.querySelectorAll('.button-container');
1295
- buttonContainers.forEach(container => {
1296
- const itemName = container.getAttribute('data-item-name');
1297
- const section = container.getAttribute('data-item-section');
1298
- const quantityDisplay = container.querySelector('.quantity-display');
1299
- const addButton = container.querySelector('.add-to-cart-btn');
1300
- const quantitySelector = container.querySelector('.quantity-selector');
1301
 
1302
- const cartItem = cart.find(item =>
1303
- item.itemName === itemName &&
1304
- item.section === section &&
1305
- item.instructions === '' &&
1306
- JSON.stringify(item.addons) === JSON.stringify([])
1307
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1308
 
1309
- if (cartItem && cartItem.quantity > 0) {
1310
- quantityDisplay.innerText = cartItem.quantity;
1311
- addButton.style.display = 'none';
1312
- quantitySelector.style.display = 'flex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1313
  } else {
1314
- quantityDisplay.innerText = '0';
1315
- addButton.style.display = 'block';
1316
- quantitySelector.style.display = 'none';
1317
  }
1318
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1319
  }
1320
 
 
1321
  document.addEventListener('DOMContentLoaded', function () {
 
 
 
 
1322
  const avatarContainer = document.querySelector('.avatar-dropdown-container');
1323
  const dropdownMenu = document.querySelector('.dropdown-menu');
 
1324
  avatarContainer.addEventListener('click', function (event) {
1325
  event.stopPropagation();
1326
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1327
  });
 
1328
  document.addEventListener('click', function (event) {
1329
  if (!avatarContainer.contains(event.target)) {
1330
  dropdownMenu.style.display = 'none';
1331
  }
1332
  });
 
 
1333
  const dropdownItems = document.querySelectorAll('.dropdown-item');
1334
  dropdownItems.forEach(item => {
1335
  item.addEventListener('click', function () {
1336
  dropdownMenu.style.display = 'none';
1337
  });
1338
  });
 
 
1339
  const menuCards = document.querySelectorAll('.menu-card');
1340
  menuCards.forEach(card => {
1341
  const itemName = card.querySelector('.card-title').innerText;
1342
  const detailsDiv = card.querySelector('.item-details');
1343
  const detailsData = menuItemDetails[itemName];
 
1344
  if (detailsData) {
1345
  detailsDiv.innerHTML = `
1346
  <h6>Ingredients</h6>
@@ -1359,6 +1833,8 @@
1359
  detailsDiv.innerHTML = '<p>No details available for this item.</p>';
1360
  }
1361
  });
 
 
1362
  const cardObserver = new IntersectionObserver((entries, observer) => {
1363
  entries.forEach(entry => {
1364
  if (entry.isIntersecting) {
@@ -1371,95 +1847,100 @@
1371
  rootMargin: '0px',
1372
  threshold: 0.1
1373
  });
1374
- const videoObserver = new IntersectionObserver((entries, observer) => {
1375
- entries.forEach(entry => {
1376
- if (entry.isIntersecting) {
1377
- const video = entry.target;
1378
- const container = video.closest('.video-container');
1379
- const src = getValidVideoUrl(video.getAttribute('data-src'));
1380
- if (src && !video.querySelector('source[src="' + src + '"]')) {
1381
- const source = video.querySelector('source');
1382
- source.src = src;
1383
- video.load().then(() => {
1384
- video.classList.add('loaded');
1385
- }).catch(err => {
1386
- console.error('Video load error:', err);
1387
- container.classList.add('video-error');
1388
- });
1389
- }
1390
- observer.unobserve(video);
1391
- }
1392
- });
1393
- }, {
1394
- root: null,
1395
- rootMargin: '200px',
1396
- threshold: 0.01
1397
- });
1398
  menuCards.forEach(card => cardObserver.observe(card));
1399
- document.querySelectorAll('.menu-video').forEach(video => videoObserver.observe(video));
 
1400
  const toggleLinks = document.querySelectorAll('.toggle-details');
1401
  toggleLinks.forEach(link => {
1402
  link.addEventListener('click', function () {
1403
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1404
  const detailsDiv = document.getElementById(`details-${itemName}`);
1405
- detailsDiv.classList.toggle('show');
1406
- this.innerText = detailsDiv.classList.contains('show') ? 'Hide Details' : 'Show Details';
 
 
1407
  });
1408
  });
 
 
1409
  const categoryButtons = document.querySelectorAll('.category-button');
1410
  const categoryForm = document.getElementById('categoryForm');
1411
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
 
1412
  if (!selectedCategoryInput.value) {
1413
  selectedCategoryInput.value = "All";
1414
  document.querySelector('.category-button[data-category="All"]').classList.add('selected');
1415
  }
 
1416
  categoryButtons.forEach(button => {
1417
  button.addEventListener('click', function () {
1418
- categoryButtons.forEach(btn => btn.classList.remove('selected'));
 
 
 
 
1419
  this.classList.add('selected');
 
1420
  selectedCategoryInput.value = this.getAttribute('data-category');
1421
  categoryForm.submit();
1422
  });
1423
  });
 
 
1424
  const searchBar = document.getElementById('searchBar');
1425
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
 
1426
  searchBar.addEventListener('input', function () {
1427
  const input = this.value.trim().toLowerCase();
1428
  suggestionsContainer.innerHTML = '';
1429
  suggestionsContainer.style.display = 'none';
 
1430
  if (input) {
1431
  const filteredItems = menuItems.filter(item =>
1432
  item.toLowerCase().includes(input)
1433
  );
 
1434
  if (filteredItems.length > 0) {
1435
  filteredItems.forEach(item => {
1436
  const suggestionDiv = document.createElement('div');
1437
  suggestionDiv.classList.add('suggestion-item');
1438
  suggestionDiv.innerText = item;
 
 
1439
  suggestionDiv.addEventListener('click', function () {
1440
  searchBar.value = item;
1441
  suggestionsContainer.style.display = 'none';
1442
  filterMenu();
1443
  });
 
1444
  suggestionsContainer.appendChild(suggestionDiv);
1445
  });
 
1446
  suggestionsContainer.style.display = 'block';
1447
  }
1448
  }
 
1449
  filterMenu();
1450
  });
 
1451
  document.addEventListener('click', function (event) {
1452
  if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
1453
  suggestionsContainer.style.display = 'none';
1454
  }
1455
  });
 
 
1456
  const descriptionTextarea = document.getElementById('custom-dish-description');
1457
  const descriptionSuggestions = document.getElementById('descriptionSuggestions');
 
1458
  if (descriptionTextarea && descriptionSuggestions) {
1459
  let usedIngredients = new Set();
 
1460
  function updateUsedIngredients() {
1461
  const inputText = descriptionTextarea.value.trim();
1462
  usedIngredients.clear();
 
1463
  if (inputText) {
1464
  const words = inputText.split(/,\s*/).map(word => word.trim());
1465
  words.forEach(word => {
@@ -1469,22 +1950,28 @@
1469
  });
1470
  }
1471
  }
 
1472
  descriptionTextarea.addEventListener('input', function () {
1473
  const inputText = this.value.trim();
1474
  const words = inputText.split(/,\s*/);
1475
  const lastWord = words[words.length - 1].trim().toLowerCase();
 
1476
  descriptionSuggestions.innerHTML = '';
1477
  descriptionSuggestions.style.display = 'none';
1478
  updateUsedIngredients();
 
1479
  if (lastWord) {
1480
  const filteredIngredients = ingredientsList.filter(ingredient =>
1481
  ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
1482
  );
 
1483
  if (filteredIngredients.length > 0) {
1484
  filteredIngredients.forEach(ingredient => {
1485
  const suggestionDiv = document.createElement('div');
1486
  suggestionDiv.classList.add('suggestion-item');
1487
  suggestionDiv.innerText = ingredient;
 
 
1488
  suggestionDiv.addEventListener('click', function () {
1489
  const currentValue = descriptionTextarea.value;
1490
  const lastCommaIndex = currentValue.lastIndexOf(',');
@@ -1494,18 +1981,23 @@
1494
  descriptionTextarea.focus();
1495
  updateUsedIngredients();
1496
  });
 
1497
  descriptionSuggestions.appendChild(suggestionDiv);
1498
  });
 
1499
  descriptionSuggestions.style.display = 'block';
1500
  }
1501
  }
1502
  });
 
1503
  document.addEventListener('click', function (event) {
1504
  if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1505
  descriptionSuggestions.style.display = 'none';
1506
  }
1507
  });
1508
  }
 
 
1509
  fetch('/cart/get')
1510
  .then(response => {
1511
  if (!response.ok) {
@@ -1527,223 +2019,91 @@
1527
  const cart = getCartLocalStorage();
1528
  updateCartUI(cart);
1529
  });
1530
- const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
1531
- preloadedVideos.forEach(link => {
1532
- const video = document.createElement('video');
1533
- video.src = link.href;
1534
- video.preload = 'auto';
1535
- });
1536
  const decreaseBtn = document.getElementById('decreaseQuantity');
1537
  const increaseBtn = document.getElementById('increaseQuantity');
1538
- const quantityInput = document.getElementById('quantityInput');
 
1539
  decreaseBtn.addEventListener('click', function () {
1540
- let currentQuantity = parseInt(quantityInput.value);
1541
  if (currentQuantity > 1) {
1542
  currentQuantity--;
1543
- quantityInput.value = currentQuantity;
1544
  }
1545
  });
 
1546
  increaseBtn.addEventListener('click', function () {
1547
- let currentQuantity = parseInt(quantityInput.value);
1548
  currentQuantity++;
1549
- quantityInput.value = currentQuantity;
1550
  });
1551
-
 
1552
  const micIcon = document.getElementById('micIcon');
 
1553
  if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
1554
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1555
  const recognition = new SpeechRecognition();
1556
  recognition.lang = 'en-US';
1557
- recognition.onstart = () => micIcon.classList.add('active');
 
 
 
 
 
1558
  recognition.onresult = (event) => {
1559
- searchBar.value = event.results[0][0].transcript.trim();
 
1560
  filterMenu();
 
1561
  };
 
1562
  recognition.onend = () => micIcon.classList.remove('active');
1563
  recognition.onerror = (event) => {
1564
  micIcon.classList.remove('active');
1565
  console.error('Speech error:', event.error);
 
1566
  };
 
1567
  micIcon.addEventListener('click', () => {
1568
- recognition.start();
 
 
 
 
 
1569
  });
1570
  } else {
1571
  micIcon.style.display = 'none';
1572
  }
1573
- });
1574
-
1575
- function filterMenu() {
1576
- const input = document.getElementById('searchBar').value.trim().toLowerCase();
1577
- const sections = document.querySelectorAll('h3');
1578
- const items = document.querySelectorAll('.menu-card');
1579
- let matchedSections = new Set();
1580
- items.forEach(item => {
1581
- const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1582
- const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1583
- if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
1584
- item.style.display = 'block';
1585
- item.classList.add('visible');
1586
- matchedSections.add(item.closest('.row'));
1587
- } else {
1588
- item.style.display = 'none';
1589
- }
1590
- });
1591
- sections.forEach(section => {
1592
- const sectionRow = section.nextElementSibling;
1593
- if (matchedSections.has(sectionRow)) {
1594
- section.style.display = 'block';
1595
- sectionRow.style.display = 'flex';
1596
- } else {
1597
- section.style.display = 'none';
1598
- sectionRow.style.display = 'none';
1599
- }
1600
  });
1601
- if (!input) {
1602
- sections.forEach(section => {
1603
- section.style.display = 'block';
1604
- section.nextElementSibling.style.display = 'flex';
1605
- });
1606
- items.forEach(item => {
1607
- item.style.display = 'block';
1608
- item.classList.add('visible');
1609
- });
1610
- }
1611
- }
1612
-
1613
- function showItemDetails(name, price, image, description, section, selectedCategory) {
1614
- document.getElementById('modal-name').innerText = name;
1615
- document.getElementById('modal-price').innerText = `$${price}`;
1616
- const modalImg = document.getElementById('modal-img');
1617
- modalImg.src = image || '/static/placeholder.jpg';
1618
- document.getElementById('modal-description').innerText = description || 'No description available.';
1619
- document.getElementById('addons-list').innerHTML = 'Loading customization options...';
1620
- document.getElementById('modal-instructions').value = '';
1621
- const modalSectionEl = document.getElementById('modal-section');
1622
- modalSectionEl.setAttribute('data-section', section);
1623
- modalSectionEl.setAttribute('data-category', selectedCategory);
1624
- document.getElementById('quantityInput').value = 1;
1625
-
1626
- fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
1627
- .then(response => response.json())
1628
- .then(data => {
1629
- const addonsList = document.getElementById('addons-list');
1630
- addonsList.innerHTML = '';
1631
- if (!data.success || !data.addons || data.addons.length === 0) {
1632
- addonsList.innerHTML = '<p>No customization options available.</p>';
1633
- return;
1634
- }
1635
- data.addons.forEach(addon => {
1636
- const sectionDiv = document.createElement('div');
1637
- sectionDiv.classList.add('addon-section');
1638
- const title = document.createElement('h6');
1639
- title.innerText = addon.name;
1640
- sectionDiv.appendChild(title);
1641
- const optionsContainer = document.createElement('div');
1642
- addon.options.forEach((option, index) => {
1643
- const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
1644
- const listItem = document.createElement('div');
1645
- listItem.classList.add('form-check');
1646
- listItem.innerHTML = `
1647
- <input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
1648
- data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
1649
- <label class="form-check-label" for="${optionId}">
1650
- ${option} ${addon.extra_charge ? `($${addon.extra_charge_amount})` : ''}
1651
- </label>
1652
- `;
1653
- optionsContainer.appendChild(listItem);
1654
  });
1655
- sectionDiv.appendChild(optionsContainer);
1656
- addonsList.appendChild(sectionDiv);
1657
- });
1658
- })
1659
- .catch(err => {
1660
- console.error('Error fetching add-ons:', err);
1661
- document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
1662
- });
1663
- }
1664
-
1665
- document.addEventListener('click', function(event) {
1666
- if (event.target.classList.contains('addon-option')) {
1667
- handleAddonClick(event.target);
1668
- }
1669
- });
1670
-
1671
- function handleAddonClick(checkbox) {
1672
- const groupName = checkbox.getAttribute('data-group');
1673
- const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it a Combo"].includes(groupName);
1674
- if (!isMultiSelectGroup) {
1675
- const checkboxes = document.querySelectorAll(`.addon-option[data-group="${groupName}"]`);
1676
- checkboxes.forEach(otherCheckbox => {
1677
- if (otherCheckbox !== checkbox) {
1678
- otherCheckbox.checked = false;
1679
  }
1680
- });
1681
- }
1682
- }
1683
-
1684
- function addToCartFromModal() {
1685
- const itemName = document.getElementById('modal-name').innerText;
1686
- let itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
1687
- if (isNaN(itemPrice)) {
1688
- alert('Invalid price for the item. Please check the item details.');
1689
- return;
1690
- }
1691
- const itemImage = document.getElementById('modal-img').src;
1692
- const modalSectionEl = document.getElementById('modal-section');
1693
- const section = modalSectionEl.getAttribute('data-section');
1694
- const selectedCategory = modalSectionEl.getAttribute('data-category');
1695
- if (!itemName || !itemPrice || !section || !itemImage) {
1696
- console.error('Missing data for cart item:', { itemName, itemPrice, section, itemImage });
1697
- return;
1698
- }
1699
- const selectedAddOns = Array.from(
1700
- document.querySelectorAll('#addons-list input[type="checkbox"]:checked')
1701
- ).map(addon => ({
1702
- name: addon.getAttribute('data-name') || 'Default Name',
1703
- price: parseFloat(addon.getAttribute('data-price') || 0)
1704
- }));
1705
- const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
1706
- const instructions = document.getElementById('modal-instructions').value;
1707
- const cartPayload = {
1708
- itemName: itemName,
1709
- itemPrice: itemPrice,
1710
- itemImage: itemImage,
1711
- section: section,
1712
- category: selectedCategory,
1713
- addons: selectedAddOns,
1714
- instructions: instructions,
1715
- quantity: quantity
1716
- };
1717
-
1718
- fetch('/cart/add', {
1719
- method: 'POST',
1720
- headers: {
1721
- 'Content-Type': 'application/json',
1722
- },
1723
- body: JSON.stringify(cartPayload)
1724
- })
1725
- .then(response => response.json())
1726
- .then(data => {
1727
- if (data.success) {
1728
- alert('Item added to cart successfully!');
1729
- updateCartUI(data.cart);
1730
- const modal = document.getElementById('itemModal');
1731
- const modalInstance = bootstrap.Modal.getInstance(modal);
1732
- modalInstance.hide();
1733
- } else {
1734
- console.error('Failed to add item to cart:', data.error);
1735
- alert(data.error || 'Failed to add item to cart.');
1736
  }
1737
- })
1738
- .catch(err => {
1739
- console.error('Error adding item to cart:', err);
1740
- const cart = addToCartLocalStorage(cartPayload);
1741
- updateCartUI(cart);
1742
- const modal = document.getElementById('itemModal');
1743
- const modalInstance = bootstrap.Modal.getInstance(modal);
1744
- modalInstance.hide();
1745
  });
1746
- }
1747
  </script>
1748
  </body>
1749
  </html>
 
7
  <!-- Bootstrap CSS -->
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
10
+
11
  <!-- Preload Critical Resources -->
12
+ <link rel="preload" href="/static/placeholder.mp4" as="video" type="video/mp4">
13
  {% for section, items in ordered_menu.items() %}
14
  {% for item in items[:1] %}
15
+ <link rel="preload" href="{{ item.Video1__c | default('/static/placeholder.mp4') }}" as="video" type="video/mp4" fetchpriority="high" media="(min-width: 768px)">
16
  {% endfor %}
17
  {% endfor %}
18
+
19
  <style>
20
+ :root {
21
+ --primary-color: #0FAA39;
22
+ --primary-hover: #0D9232;
23
+ --primary-active: #0B7A29;
24
+ --secondary-color: #FFA07A;
25
+ --secondary-hover: #FF8C61;
26
+ --background-color: #fdf4e3;
27
+ --text-color: #333333;
28
+ --card-shadow: 0 4px 8px rgba(0,0,0,0.1);
29
+ }
30
+
31
  body {
32
+ font-family: 'Segoe UI', Arial, sans-serif;
33
+ background-color: var(--background-color);
34
  margin: 0;
35
  padding: 0;
36
  display: flex;
37
  flex-direction: column;
38
  padding-bottom: 70px;
39
+ color: var(--text-color);
40
  }
41
+
42
  .container {
43
  max-width: 900px;
44
  }
45
+
46
  .menu-card {
47
  max-width: 350px;
48
  border-radius: 15px;
 
53
  flex-direction: column;
54
  opacity: 0;
55
  transition: opacity 0.3s ease-in-out;
56
+ box-shadow: var(--card-shadow);
57
  }
58
+
59
  .menu-card.visible {
60
  opacity: 1;
61
  }
62
+
63
  .video-container {
 
64
  position: relative;
65
+ width: 100%;
 
 
 
 
 
 
 
 
 
 
 
66
  height: 200px;
67
+ overflow: hidden;
68
+ border-radius: 15px 15px 0 0;
69
+ background-color: #000;
70
+ transition: all 0.3s ease;
71
+ }
72
+
73
+ .video-container video {
74
  width: 100%;
75
+ height: 100%;
76
  object-fit: cover;
 
77
  opacity: 0;
78
  transition: opacity 0.5s ease-in-out;
 
79
  }
80
+
81
+ .video-container.video-loaded video {
82
  opacity: 1;
83
  }
84
+
85
+ .video-container .video-fallback {
86
+ position: absolute;
87
+ top: 0;
88
+ left: 0;
89
+ width: 100%;
90
+ height: 100%;
91
+ object-fit: cover;
92
+ opacity: 0;
93
+ transition: opacity 0.3s ease;
94
+ }
95
+
96
+ .video-container.video-error .video-fallback {
97
  opacity: 1;
 
98
  }
99
+
100
+ .video-container.video-error video {
101
+ display: none;
102
+ }
103
+
104
  .menu-card .card-body .card-title {
105
  font-size: 1.2rem;
106
  font-weight: 600;
107
  margin: 10px 0;
108
+ color: var(--text-color);
109
  }
110
+
111
  .menu-card .card-body .card-text.price {
112
  font-size: 1rem;
113
  font-weight: 500;
114
  color: #000000;
115
  margin-bottom: 5px;
116
  }
117
+
118
  .addbutton .btn {
119
+ background-color: var(--primary-color);
120
  color: white;
121
  padding: 10px 20px;
122
  font-size: 16px;
 
126
  transition: background-color 0.3s ease;
127
  margin-left: 13px;
128
  }
129
+
130
  .addbutton .btn:hover {
131
+ background-color: var(--primary-hover);
132
  }
133
+
134
  .button-container {
135
  display: flex;
136
  flex-direction: column;
 
138
  justify-content: center;
139
  gap: 6px;
140
  }
141
+
142
  .customisable-text {
143
+ color: var(--primary-color);
144
  font-size: 10px;
145
  font-weight: 500;
146
  margin: 0;
147
  text-align: center;
148
  line-height: 1;
149
  }
150
+
151
  .btn-primary {
152
  font-size: 12px;
153
  font-weight: bold;
154
  border-radius: 8px;
155
  width: 70px;
156
  height: 35px;
157
+ background-color: var(--primary-color);
158
+ border-color: var(--primary-color);
159
  display: flex;
160
  align-items: center;
161
  justify-content: center;
162
  padding: 0;
163
  transition: background-color 0.3s ease, transform 0.1s ease;
164
  }
165
+
166
  .btn-primary:hover {
167
+ background-color: var(--primary-hover);
168
+ border-color: var(--primary-hover);
 
169
  }
170
+
171
  .btn-primary:active,
172
  .btn-primary:focus {
173
+ background-color: var(--primary-active);
174
+ border-color: var(--primary-active);
175
  box-shadow: none;
176
  transform: scale(0.98);
177
  }
178
+
179
  .avatar-dropdown-container {
180
  position: absolute;
181
  right: 10px;
 
185
  align-items: right;
186
  justify-content: center;
187
  }
188
+
189
  .avatar-icon {
190
  width: 40px;
191
  height: 40px;
 
199
  font-size: 20px;
200
  font-weight: bold;
201
  }
202
+
203
  .dropdown-menu {
204
  position: absolute;
205
  right: 0;
 
211
  display: none;
212
  border: 1px solid #ffd8b1;
213
  }
214
+
215
  .dropdown-menu .dropdown-item {
216
  padding: 12px 16px;
217
  text-decoration: none;
 
221
  font-size: 15px;
222
  transition: background-color 0.2s ease;
223
  }
224
+
225
  .dropdown-menu .dropdown-item:last-child {
226
  border-bottom: none;
227
  }
228
+
229
  .dropdown-menu .dropdown-item:hover {
230
  background-color: #ffe4c4;
231
  color: #333;
232
  }
233
+
234
  .fixed-top-bar {
235
  position: relative;
236
  top: 0;
237
  left: 0;
238
  width: 100%;
239
  height: 54px;
240
+ background: linear-gradient(45deg, var(--secondary-color), #FFB347);
241
  color: white;
242
  padding: 15px;
243
  display: flex;
 
245
  align-items: center;
246
  z-index: 1000;
247
  }
248
+
249
  .search-bar-container {
250
  position: absolute;
251
  left: 20px;
 
257
  max-width: 90%;
258
  position: relative;
259
  }
260
+
261
  .search-bar-container input {
262
  width: 100%;
263
  padding: 8px 40px 8px 40px;
 
268
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
269
  outline: none;
270
  }
271
+
272
  .search-bar-container input::placeholder {
273
  color: #888;
274
  }
275
+
276
  .search-icon {
277
  position: absolute;
278
  left: 15px;
279
  font-size: 18px;
280
  color: #888;
281
  }
282
+
283
  .mic-icon {
284
  position: absolute;
285
  right: 15px;
 
288
  cursor: pointer;
289
  transition: color 0.3s ease;
290
  }
291
+
292
  .mic-icon.active {
293
  color: #007bff;
294
  }
295
+
296
  .autocomplete-suggestions {
297
  position: absolute;
298
  top: 100%;
 
307
  z-index: 1000;
308
  display: none;
309
  }
310
+
311
  .autocomplete-suggestions .suggestion-item {
312
  padding: 8px 15px;
313
  cursor: pointer;
314
  font-size: 14px;
315
  color: #333;
316
  }
317
+
318
  .autocomplete-suggestions .suggestion-item:hover {
319
  background-color: #f1f1f1;
320
  }
321
+
322
  .addon-section {
323
  background-color: #fff;
324
  border: 2px solid #ffa500;
 
326
  padding: 12px;
327
  margin-bottom: 10px;
328
  }
329
+
330
  .addon-section h6 {
331
  margin-bottom: 10px;
332
  font-size: 1.1rem;
333
  font-weight: bold;
334
  color: #343a40;
335
  }
336
+
337
  .addon-section .form-check {
338
  display: inline-flex;
339
  align-items: center;
340
  margin-left: 10px;
341
  color: #343a40;
342
  }
343
+
344
  .addon-section .form-check-input {
345
  -webkit-appearance: none;
346
  -moz-appearance: none;
 
353
  position: relative;
354
  margin-right: 10px;
355
  }
356
+
357
  .addon-section .form-check-input:checked {
358
  background-color: #006400;
359
  border-color: #006400;
360
  }
361
+
362
  .addon-section .form-check-input:checked::before {
363
  content: '\2713';
364
  font-size: 14px;
 
367
  left: 4px;
368
  color: white;
369
  }
370
+
371
  .addon-section .form-check-label {
372
  font-size: 16px;
373
  margin-left: 5px;
 
376
  display: inline-block;
377
  vertical-align: middle;
378
  }
379
+
380
  form.text-center.mb-4 {
381
  display: flex;
382
  flex-direction: column;
 
384
  justify-content: center;
385
  margin-bottom: 5px;
386
  }
387
+
388
  .modal-header {
389
  padding: 10px 15px;
390
  }
391
+
392
  .modal-title {
393
  font-size: 16px;
394
  font-weight: bold;
395
  }
396
+
397
  .modal-body {
398
  max-height: 60vh;
399
  overflow-y: auto;
400
  padding: 15px;
401
  }
402
+
403
  .modal-body #modal-img {
404
  max-height: 200px;
405
  width: 100%;
 
407
  border-radius: 8px;
408
  margin-bottom: 10px;
409
  }
410
+
411
  .modal-body #modal-name {
412
  font-size: 20px;
413
  font-weight: bold;
 
415
  margin-bottom: 5px;
416
  color: #333333;
417
  }
418
+
419
  .modal-body #modal-price {
420
  font-size: 16px;
421
  font-weight: 500;
 
423
  text-align: center;
424
  margin-bottom: 10px;
425
  }
426
+
427
  .modal-body #modal-description {
428
  font-size: 14px;
429
  color: #6c757d;
430
  margin-bottom: 10px;
431
  }
432
+
433
  .modal-body .nutritional-info {
434
  font-size: 12px;
435
  color: #6c757d;
436
  margin-bottom: 10px;
437
  }
438
+
439
  .modal-body #modal-addons h6,
440
  .modal-body #first-row h6 {
441
  font-size: 14px;
442
  font-weight: bold;
443
  margin-bottom: 10px;
444
  }
445
+
446
  .modal-footer {
447
  display: flex;
448
  align-items: center;
449
  justify-content: space-between;
450
  padding: 10px;
451
  }
452
+
453
  .modal-footer .d-flex {
454
  display: flex;
455
  align-items: center;
456
  gap: 10px;
457
  }
458
+
459
  .modal-footer .btn {
460
  height: 40px;
461
  padding: 0 15px;
462
  }
463
+
464
  .modal-footer .form-control {
465
  width: 50px;
466
  height: 40px;
467
  text-align: center;
468
  }
469
+
470
  .modal-footer .btn-primary {
471
+ background-color: var(--primary-color);
472
+ border-color: var(--primary-color);
473
  font-weight: bold;
474
  padding: 10px 20px;
475
  height: 40px;
 
478
  align-items: center;
479
  width: auto;
480
  }
481
+
482
  .modal-footer .btn-outline-secondary {
483
  height: 40px;
484
  width: 40px;
485
  }
486
+
487
  .item-details {
488
  background-color: #f8f9fa;
489
  border-radius: 8px;
 
491
  margin: 10px 15px;
492
  display: none;
493
  }
494
+
495
  .item-details.show {
496
  display: block;
497
  }
498
+
499
  .item-details h6 {
500
  font-size: 0.9rem;
501
  font-weight: bold;
502
  margin-bottom: 5px;
503
  color: #333333;
504
  }
505
+
506
  .item-details p {
507
  font-size: 0.85rem;
508
  margin-bottom: 5px;
509
  color: #333;
510
  }
511
+
512
  .item-details .nutritional-info {
513
  display: grid;
514
  grid-template-columns: 1fr 1fr;
 
519
  font-size: 0.85rem;
520
  line-height: 1.5;
521
  }
522
+
523
  .toggle-details {
524
  cursor: pointer;
525
+ color: var(--primary-color);
526
  font-size: 0.9rem;
527
  margin-left: 15px;
528
  margin-bottom: 10px;
529
  display: inline-block;
530
  }
531
+
532
  .toggle-details:hover {
533
  text-decoration: underline;
534
  }
535
+
536
  .category-buttons {
537
  display: flex;
538
  flex-wrap: wrap;
 
540
  justify-content: center;
541
  margin-top: 10px;
542
  }
543
+
544
  .category-button {
545
  background-color: #fff;
546
+ border: 2px solid var(--primary-color);
547
+ color: var(--primary-color);
548
  padding: 5px 15px;
549
  border-radius: 20px;
550
  font-size: 0.9rem;
551
  cursor: pointer;
552
  transition: background-color 0.3s, color 0.3s;
553
  }
554
+
555
  .category-button.selected {
556
+ background-color: var(--primary-color);
557
  color: #fff;
558
+ border-color: var(--primary-color);
559
  }
560
+
561
  .category-button:hover {
562
  background-color: #e6f4ea;
563
  }
564
+
565
  .quantity-selector {
566
  display: flex;
567
  align-items: center;
568
  gap: 5px;
569
  }
570
+
571
  .quantity-selector .btn {
572
  width: 25px;
573
  height: 25px;
 
576
  line-height: 25px;
577
  text-align: center;
578
  }
579
+
580
  .quantity-selector .quantity-display {
581
  width: 25px;
582
  text-align: center;
 
584
  font-weight: bold;
585
  line-height: 25px;
586
  }
587
+
588
  .quantity-selector .quantity-to-add,
589
  .quantity-selector .quantity-to-remove {
590
  width: 45px;
 
592
  font-size: 12px;
593
  padding: 0 5px;
594
  }
595
+
596
  .modal-dialog {
597
  max-height: 90vh;
598
  }
599
+
600
  .modal-body::-webkit-scrollbar {
601
  width: 8px;
602
  }
603
+
604
  .modal-body::-webkit-scrollbar-track {
605
  background: #f1f1f1;
606
  border-radius: 10px;
607
  }
608
+
609
  .modal-body::-webkit-scrollbar-thumb {
610
+ background: var(--primary-color);
611
  border-radius: 10px;
612
  }
613
+
614
  .modal-body::-webkit-scrollbar-thumb:hover {
615
+ background: var(--primary-hover);
616
  }
617
+
618
  .btn-primary:disabled {
619
  opacity: 0.65;
620
  cursor: not-allowed;
621
  }
622
+
623
  .quantity-selector select {
624
  width: 60px;
625
  height: 35px;
 
627
  border-radius: 5px;
628
  border: 1px solid #ced4da;
629
  }
630
+
631
  #custom-dish-form {
632
  position: relative;
633
  padding-bottom: 80px;
634
  }
635
+
636
  #custom-dish-form .btn-primary {
637
  position: absolute;
638
  right: 15px;
 
640
  width: auto;
641
  padding: 10px 20px;
642
  }
643
+
644
  .bottom-action-bar {
645
  position: fixed;
646
  bottom: 0;
 
656
  max-width: 900px;
657
  margin: 0 auto;
658
  }
659
+
660
  .bottom-action-bar .btn {
661
  flex: 1;
662
  margin: 0 5px;
 
672
  min-width: 0;
673
  white-space: nowrap;
674
  }
675
+
676
  .bottom-action-bar .btn-order-history {
677
+ background-color: var(--secondary-color);
678
+ border-color: var(--secondary-color);
679
  }
680
+
681
  .bottom-action-bar .btn-order-history:hover {
682
+ background-color: var(--secondary-hover);
683
+ border-color: var(--secondary-hover);
684
  }
685
+
686
  .bottom-action-bar .btn-view-cart {
687
+ background-color: var(--primary-color);
688
+ border-color: var(--primary-color);
689
  }
690
+
691
  .bottom-action-bar .btn-view-cart:hover {
692
+ background-color: var(--primary-hover);
693
+ border-color: var(--primary-hover);
694
  }
695
+
696
  .cart-icon-badge {
697
  background-color: white;
698
+ color: var(--primary-color);
699
  border-radius: 50%;
700
  width: 20px;
701
  height: 20px;
 
705
  font-size: 12px;
706
  margin-left: 8px;
707
  }
708
+
709
+ /* Toast notification styles */
710
+ .toast-container {
711
+ position: fixed;
712
+ top: 20px;
713
+ right: 20px;
714
+ z-index: 1100;
715
+ }
716
+
717
+ .toast {
718
+ background-color: #333;
719
+ color: white;
720
+ padding: 12px 20px;
721
+ border-radius: 4px;
722
+ margin-bottom: 10px;
723
+ opacity: 0;
724
+ transition: opacity 0.3s ease;
725
+ max-width: 300px;
726
+ }
727
+
728
+ .toast.show {
729
+ opacity: 1;
730
+ }
731
+
732
+ .toast.success {
733
+ background-color: var(--primary-color);
734
+ }
735
+
736
+ .toast.error {
737
+ background-color: #dc3545;
738
+ }
739
+
740
+ /* Accessibility focus states */
741
+ .video-container:focus-within {
742
+ outline: 2px solid #3a86ff;
743
+ outline-offset: 2px;
744
+ }
745
+
746
+ /* Reduced motion preferences */
747
+ @media (prefers-reduced-motion: reduce) {
748
+ * {
749
+ animation-duration: 0.01ms !important;
750
+ animation-iteration-count: 1 !important;
751
+ transition-duration: 0.01ms !important;
752
+ scroll-behavior: auto !important;
753
+ }
754
+
755
+ .video-container video {
756
+ transition: none;
757
+ }
758
+
759
+ .menu-card {
760
+ transition: none;
761
+ }
762
+ }
763
+
764
  @media (max-width: 576px) {
765
  .fixed-top-bar {
766
  height: 60px;
767
  padding: 10px;
768
  }
769
+
770
  .search-bar-container {
771
  width: 80%;
772
  max-width: 100%;
 
774
  top: 50%;
775
  transform: translateY(-50%);
776
  }
777
+
778
  .search-bar-container input {
779
  padding: 6px 35px 6px 35px;
780
  font-size: 14px;
781
  border-radius: 20px;
782
  }
783
+
784
  .search-icon {
785
  left: 12px;
786
  font-size: 16px;
787
  }
788
+
789
  .mic-icon {
790
  right: 12px;
791
  font-size: 16px;
792
  }
793
+
794
  .avatar-dropdown-container {
795
  right: 10px;
796
  }
797
+
798
  .avatar-icon {
799
  width: 40px;
800
  height: 40px;
801
  font-size: 20px;
802
  }
803
+
804
  .dropdown-menu {
805
  width: 220px;
806
  }
807
+
808
  .dropdown-menu .dropdown-item {
809
  padding: 12px 16px;
810
  font-size: 15px;
811
  }
812
+
813
  .category-buttons {
814
  gap: 8px;
815
  }
816
+
817
  .category-button {
818
  padding: 4px 12px;
819
  font-size: 0.85rem;
820
  }
821
+
822
  .modal-dialog {
823
  max-width: 96%;
824
  margin: 5px auto;
825
  }
826
+
827
  .modal-header {
828
  padding: 5px 10px;
829
  }
830
+
831
  .modal-title {
832
  font-size: 14px;
833
  }
834
+
835
  .modal-body {
836
  max-height: 50vh;
837
  padding: 8px;
838
  }
839
+
840
  .modal-body #modal-img {
841
  max-height: 150px;
842
  width: 100%;
 
844
  margin: 0 auto 5px;
845
  display: block;
846
  }
847
+
848
  .modal-body #modal-name {
849
  font-size: 18px;
850
  margin-bottom: 3px;
851
  }
852
+
853
  .modal-body #modal-price {
854
  font-size: 14px;
855
  margin-bottom: 5px;
856
  }
857
+
858
  .modal-body #modal-description {
859
  font-size: 12px;
860
  margin-bottom: 5px;
861
  }
862
+
863
  .modal-body .nutritional-info {
864
  font-size: 10px;
865
  margin-bottom: 5px;
866
  }
867
+
868
  .modal-body #modal-addons h6,
869
  .modal-body #first-row h6 {
870
  font-size: 12px;
871
  margin-bottom: 5px;
872
  }
873
+
874
  .modal-footer {
875
  padding: 5px;
876
  }
877
+
878
  .modal-footer .btn {
879
  height: 30px;
880
  padding: 0 10px;
881
  }
882
+
883
  .modal-footer .form-control {
884
  width: 30px;
885
  height: 30px;
886
  font-size: 12px;
887
  font-weight: bold;
888
  }
889
+
890
  .modal-footer .btn-outline-secondary {
891
  width: 25px;
892
  height: 25px;
893
  font-size: 12px;
894
  line-height: 25px;
895
  }
896
+
897
  .modal-footer .btn-primary {
898
  font-size: 12px;
899
  height: 30px;
900
  padding: 0 15px;
901
  border-radius: 5px;
902
  }
903
+
904
  .btn-primary {
905
  font-size: 10px;
906
  width: 50px;
907
  height: 25px;
908
  }
909
+
910
  .customisable-text {
911
  font-size: 8px;
912
  }
913
+
914
  .button-container {
915
  gap: 3px;
916
  }
917
+
918
  .quantity-selector .btn {
919
  width: 18px;
920
  height: 18px;
921
  font-size: 9px;
922
  line-height: 18px;
923
  }
924
+
925
  .quantity-selector .quantity-display {
926
  width: 18px;
927
  font-size: 9px;
928
  line-height: 18px;
929
  }
930
+
931
  .quantity-selector .quantity-to-add,
932
  .quantity-selector .quantity-to-remove {
933
  width: 35px;
934
  height: 18px;
935
  font-size: 9px;
936
  }
937
+
938
  .quantity-selector select {
939
  width: 50px;
940
  height: 30px;
941
  font-size: 12px;
942
  }
943
+
944
  .bottom-action-bar {
945
  padding: 8px 10px;
946
  }
947
+
948
  .bottom-action-bar .btn {
949
  padding: 8px 10px;
950
  font-size: 14px;
951
  }
952
+
953
  .cart-icon-badge {
954
  width: 18px;
955
  height: 18px;
 
960
  </style>
961
  </head>
962
  <body>
963
+
964
+ <!-- Toast Notification Container -->
965
+ <div class="toast-container" id="toastContainer"></div>
966
+
967
  <div class="fixed-top-bar">
968
  <div class="avatar-dropdown-container">
969
+ <div class="avatar-icon" tabindex="0" aria-label="User menu">
970
  <span>{{ first_letter }}</span>
971
  </div>
972
+ <div class="dropdown-menu" role="menu">
973
+ <a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item" role="menuitem">View Profile</a>
974
+ <a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item" role="menuitem">Order History</a>
975
+ <a href="{{ url_for('logout') }}" class="dropdown-item" role="menuitem">Logout</a>
976
  </div>
977
  </div>
978
  <div class="search-bar-container">
979
+ <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..."
980
+ aria-label="Search menu items" autocomplete="off">
981
  <i class="bi bi-search search-icon"></i>
982
+ <i class="bi bi-mic mic-icon" id="micIcon" aria-label="Voice search"></i>
983
+ <div id="autocompleteSuggestions" class="autocomplete-suggestions" role="listbox"></div>
984
  </div>
985
  </div>
986
 
987
  <form method="get" action="/menu" class="text-center mb-4" id="categoryForm">
988
+ <label class="form-label fw-bold" for="selectedCategoryInput">Select a Category:</label>
989
  <div class="category-buttons">
990
  {% for category in categories %}
991
+ <button type="button" class="category-button {% if selected_category == category %}selected{% endif %}"
992
+ data-category="{{ category }}" aria-pressed="{% if selected_category == category %}true{% else %}false{% endif %}">
993
+ {{ category }}
994
+ </button>
995
  {% endfor %}
996
+ <button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}"
997
+ data-category="Customized Dish" aria-pressed="{% if selected_category == 'Customized Dish' %}true{% else %}false{% endif %}">
998
+ Customized Dish
999
+ </button>
1000
  </div>
1001
  <input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
1002
  </form>
 
1004
  <div class="container mt-4">
1005
  {% if selected_category == "Customized Dish" %}
1006
  <div id="custom-dish-form" class="mt-4">
1007
+ <h2>Create Your Custom Dish</h2>
1008
  <form method="POST" action="/customdish/generate_custom_dish">
1009
  <div class="mb-3">
1010
  <label for="custom-dish-name" class="form-label">Dish Name</label>
1011
+ <input type="text" class="form-control" id="custom-dish-name" name="name" required
1012
+ aria-describedby="dishNameHelp">
1013
+ <div id="dishNameHelp" class="form-text">Enter a name for your custom dish</div>
1014
  </div>
1015
  <div class="mb-3 position-relative">
1016
  <label for="custom-dish-description" class="form-label">Dish Description</label>
1017
+ <textarea class="form-control" id="custom-dish-description" name="description" required
1018
+ aria-describedby="dishDescHelp"></textarea>
1019
+ <div id="dishDescHelp" class="form-text">Describe your dish and list ingredients</div>
1020
+ <div id="descriptionSuggestions" class="autocomplete-suggestions" role="listbox"></div>
1021
  </div>
1022
  <button type="submit" class="btn btn-primary">Submit Custom Dish</button>
1023
  </form>
1024
  </div>
1025
  {% else %}
1026
  {% if ordered_menu.items()|length == 0 %}
1027
+ <p class="text-center">No menu items available for this category.</p>
1028
  {% else %}
1029
  {% for section, items in ordered_menu.items() %}
1030
+ <h3 id="section-{{ section|slugify }}">{{ section }}</h3>
1031
+ <div class="row" aria-labelledby="section-{{ section|slugify }}">
1032
  {% for item in items %}
1033
  <div class="col-md-6 mb-4">
1034
  <div class="card menu-card">
1035
+ <div class="video-container"
1036
+ data-video-src="{{ item.Video1__c | default('/static/placeholder.mp4') }}"
1037
+ data-poster-src="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
1038
+ aria-label="Video demonstration of {{ item.Name | default('Item') }}"
1039
+ tabindex="0">
1040
+ <video class="lazy-video"
1041
+ muted
1042
+ loop
1043
+ preload="none"
1044
+ width="350"
1045
+ height="200"
1046
+ aria-hidden="true"
1047
+ disablePictureInPicture
1048
+ playsinline>
1049
+ <source type="video/mp4">
 
 
 
1050
  </video>
1051
  <img class="video-fallback"
1052
+ src="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
1053
+ alt="{{ item.Name | default('Item') }}"
1054
+ loading="lazy">
1055
  </div>
1056
  <div class="addbutton">
1057
  <div class="card-body d-flex align-items-center justify-content-between">
1058
  <div>
1059
+ <h3 class="card-title">{{ item.Name | default('Unnamed Item') }}</h3>
1060
  <p class="card-text">${{ item.Price__c | default('0.00') }}</p>
1061
  </div>
1062
  <div class="d-flex flex-column align-item-center justify-content-center">
1063
  <div class="button-container"
1064
  data-item-name="{{ item.Name | default('Unnamed Item') }}"
1065
+ data-item-price="{{ item.Price__c | default('0.00') }}"
1066
  data-item-image="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
1067
  data-item-section="{{ item.Section__c | default(section) }}"
1068
  data-item-category="{{ selected_category }}">
1069
  {% if item.Section__c == 'Soft Drinks' %}
1070
+ <button class="btn btn-primary add-to-cart-btn"
1071
+ onclick="handleSoftDrinkAdd(this)"
1072
+ aria-label="Add {{ item.Name }} to cart">
1073
+ ADD
1074
+ </button>
1075
  <div class="quantity-selector" style="display: none;">
1076
+ <button class="btn btn-outline-secondary decrease-btn"
1077
+ onclick="decreaseQuantity(this)"
1078
+ aria-label="Decrease quantity">-</button>
1079
+ <span class="quantity-display" aria-live="polite">0</span>
1080
+ <button class="btn btn-outline-secondary increase-btn"
1081
+ onclick="increaseQuantity(this)"
1082
+ aria-label="Increase quantity">+</button>
1083
  </div>
1084
  {% else %}
1085
  <button class="btn btn-primary"
1086
  data-bs-toggle="modal"
1087
  data-bs-target="#itemModal"
1088
+ 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 }}')"
1089
+ aria-label="Customize and add {{ item.Name }} to cart">
1090
  ADD
1091
  </button>
1092
  {% endif %}
 
1097
  </div>
1098
  </div>
1099
  </div>
1100
+ <button class="toggle-details"
1101
+ data-item-name="{{ item.Name | default('Unnamed Item') }}"
1102
+ aria-expanded="false"
1103
+ aria-controls="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">
1104
+ Show Details
1105
+ </button>
1106
+ <div class="item-details"
1107
+ id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}"
1108
+ role="region"></div>
1109
  </div>
1110
  </div>
1111
  {% endfor %}
 
1116
  </div>
1117
 
1118
  <div class="bottom-action-bar">
1119
+ <a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history" aria-label="View order history">
1120
+ <i class="bi bi-clock-history" aria-hidden="true"></i> Order History
1121
  </a>
1122
+ <a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart" aria-label="View shopping cart">
1123
+ <i class="bi bi-cart" aria-hidden="true"></i> View Cart
1124
+ <span class="cart-icon-badge" id="cart-item-count" aria-live="polite">0</span>
1125
  </a>
1126
  </div>
1127
 
1128
  <!-- Modal for Item Details -->
1129
+ <div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true" role="dialog">
1130
+ <div class="modal-dialog modal-dialog-centered" role="document">
1131
  <div class="modal-content">
1132
  <div class="modal-header">
1133
+ <h2 id="itemModalLabel" class="modal-title">Item Details</h2>
1134
  <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
1135
  </div>
1136
  <div class="modal-body">
1137
+ <div aria-live="polite" aria-atomic="true">
1138
+ <img id="modal-img" class="img-fluid rounded mb-3 d-block mx-auto" alt="" role="presentation">
1139
+ <h3 id="modal-name"></h3>
1140
+ <p id="modal-price" aria-label="Price"></p>
1141
+ </div>
1142
+
1143
  <p id="modal-description" class="text-secondary"></p>
1144
+
1145
+ <div id="modal-addons" role="group" aria-labelledby="addons-heading">
1146
+ <h4 id="addons-heading">Customization Options</h4>
1147
  <div id="addons-list" class="addons-container">Loading customization options...</div>
1148
  </div>
1149
+
1150
+ <div role="group" aria-labelledby="instructions-heading">
1151
+ <h4 id="instructions-heading">Special Instructions</h4>
1152
+ <textarea id="modal-instructions" class="form-control"
1153
+ aria-label="Enter any special instructions here..."></textarea>
1154
  </div>
1155
+
1156
  <span id="modal-section" data-section="" data-category="" style="display: none;"></span>
1157
  </div>
1158
+
1159
  <div class="modal-footer d-flex align-items-center justify-content-between">
1160
  <div class="d-flex align-items-center gap-2">
1161
+ <button type="button" class="btn btn-outline-secondary" id="decreaseQuantity"
1162
+ aria-label="Decrease quantity">-</button>
1163
+ <span id="quantityValue" class="form-control text-center" aria-live="polite">1</span>
1164
+ <button type="button" class="btn btn-outline-secondary" id="increaseQuantity"
1165
+ aria-label="Increase quantity">+</button>
1166
  </div>
1167
+
1168
+ <button type="button" class="btn btn-primary" onclick="addToCartFromModal()"
1169
+ aria-label="Add to cart">
1170
+ Add to Cart
1171
+ </button>
1172
  </div>
1173
  </div>
1174
  </div>
 
1176
 
1177
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
1178
  <script>
1179
+ // Global variables
 
1180
  const menuItems = [
1181
  {% for section, items in ordered_menu.items() %}
1182
  {% for item in items %}
 
1294
  }
1295
  };
1296
 
1297
+ // Cart operations queue
1298
+ const cartQueue = [];
1299
+ let isProcessingCart = false;
1300
+
1301
+ // Toast notification system
1302
+ function showToast(message, type = 'success', duration = 3000) {
1303
+ const toastContainer = document.getElementById('toastContainer');
1304
+ const toast = document.createElement('div');
1305
+ toast.className = `toast ${type}`;
1306
+ toast.textContent = message;
1307
+ toast.setAttribute('role', 'alert');
1308
+ toast.setAttribute('aria-live', 'assertive');
1309
+ toastContainer.appendChild(toast);
1310
+
1311
+ // Trigger reflow to enable transition
1312
+ setTimeout(() => toast.classList.add('show'), 10);
1313
+
1314
+ // Auto-dismiss after duration
1315
+ setTimeout(() => {
1316
+ toast.classList.remove('show');
1317
+ setTimeout(() => toast.remove(), 300);
1318
+ }, duration);
1319
+ }
1320
+
1321
+ // Video handling functions
1322
+ function initializeVideoObservers() {
1323
+ const videoObserver = new IntersectionObserver((entries) => {
1324
+ entries.forEach(entry => {
1325
+ if (entry.isIntersecting) {
1326
+ const container = entry.target;
1327
+ const video = container.querySelector('.lazy-video');
1328
+ const videoSrc = container.getAttribute('data-video-src');
1329
+ const posterSrc = container.getAttribute('data-poster-src');
1330
+
1331
+ if (!video.src && videoSrc) {
1332
+ video.poster = posterSrc;
1333
+ video.querySelector('source').src = videoSrc;
1334
+ video.load();
1335
+
1336
+ video.addEventListener('loadeddata', () => {
1337
+ container.classList.add('video-loaded');
1338
+ }, { once: true });
1339
+
1340
+ video.addEventListener('error', () => {
1341
+ handleVideoError(container);
1342
+ });
1343
+ }
1344
+
1345
+ videoObserver.unobserve(container);
1346
+ }
1347
+ });
1348
+ }, {
1349
+ rootMargin: '200px',
1350
+ threshold: 0.01
1351
+ });
1352
+
1353
+ document.querySelectorAll('.video-container').forEach(container => {
1354
+ videoObserver.observe(container);
1355
+ });
1356
  }
1357
 
1358
+ function handleVideoHover(container, shouldPlay) {
1359
  const video = container.querySelector('video');
1360
+ if (!video.src) return;
1361
 
1362
  try {
1363
+ if (shouldPlay) {
1364
+ video.currentTime = 0;
1365
+ video.play().catch(e => console.debug('Autoplay prevented:', e));
 
 
 
 
 
 
 
 
 
 
1366
  } else {
1367
  video.pause();
 
 
 
1368
  }
1369
  } catch (e) {
1370
+ console.error('Video control error:', e);
 
 
 
 
 
1371
  }
1372
  }
1373
 
1374
+ function handleVideoError(container) {
1375
+ container.classList.add('video-error');
1376
+ const fallback = container.querySelector('.video-fallback');
1377
+ if (fallback) {
1378
+ fallback.style.opacity = '1';
1379
+ }
1380
+ }
1381
+
1382
+ // Cart management functions
1383
  function addToCartLocalStorage(payload) {
1384
  let cart = JSON.parse(localStorage.getItem('cart')) || [];
1385
  const existingItem = cart.find(item =>
 
1387
  item.instructions === payload.instructions &&
1388
  JSON.stringify(item.addons) === JSON.stringify(payload.addons)
1389
  );
1390
+
1391
  if (existingItem) {
1392
+ existingItem.quantity += payload.quantity;
1393
  } else {
1394
  cart.push(payload);
1395
  }
1396
+
1397
  localStorage.setItem('cart', JSON.stringify(cart));
1398
  return cart;
1399
  }
 
1405
  item.instructions === instructions &&
1406
  JSON.stringify(item.addons) === JSON.stringify(addons)
1407
  );
1408
+
1409
  if (itemIndex !== -1) {
1410
  if (quantityToRemove >= cart[itemIndex].quantity) {
1411
  cart.splice(itemIndex, 1);
 
1413
  cart[itemIndex].quantity -= quantityToRemove;
1414
  }
1415
  }
1416
+
1417
  localStorage.setItem('cart', JSON.stringify(cart));
1418
  return cart;
1419
  }
 
1422
  return JSON.parse(localStorage.getItem('cart')) || [];
1423
  }
1424
 
1425
+ async function processCartQueue() {
1426
+ if (isProcessingCart || cartQueue.length === 0) return;
1427
+
1428
+ isProcessingCart = true;
1429
+ const operation = cartQueue.shift();
1430
+
1431
+ try {
1432
+ const response = await fetch(operation.url, {
1433
+ method: operation.method,
1434
+ headers: {
1435
+ 'Content-Type': 'application/json',
1436
+ },
1437
+ body: operation.payload ? JSON.stringify(operation.payload) : null
1438
+ });
1439
+
1440
+ const data = await response.json();
1441
+
1442
+ if (data.success) {
1443
+ updateCartUI(data.cart);
1444
+ operation.resolve(data);
1445
+ } else {
1446
+ console.error('Cart operation failed:', data.error);
1447
+ operation.reject(data.error);
1448
+
1449
+ // Fallback to localStorage
1450
+ if (operation.method === 'POST') {
1451
+ const cart = addToCartLocalStorage(operation.payload);
1452
+ updateCartUI(cart);
1453
+ } else if (operation.method === 'DELETE') {
1454
+ const cart = removeFromCartLocalStorage(
1455
+ operation.payload.itemName,
1456
+ operation.payload.quantity,
1457
+ operation.payload.instructions,
1458
+ operation.payload.addons
1459
+ );
1460
+ updateCartUI(cart);
1461
+ }
1462
+ }
1463
+ } catch (err) {
1464
+ console.error('Network error:', err);
1465
+ operation.reject(err);
1466
+
1467
+ // Fallback to localStorage
1468
+ if (operation.method === 'POST') {
1469
+ const cart = addToCartLocalStorage(operation.payload);
1470
+ updateCartUI(cart);
1471
+ }
1472
+ } finally {
1473
+ isProcessingCart = false;
1474
+ processCartQueue();
1475
+ }
1476
+ }
1477
+
1478
+ function enqueueCartOperation(operation) {
1479
+ return new Promise((resolve, reject) => {
1480
+ cartQueue.push({
1481
+ ...operation,
1482
+ resolve,
1483
+ reject
1484
+ });
1485
+ processCartQueue();
1486
+ });
1487
+ }
1488
+
1489
+ // UI update functions
1490
+ function updateCartUI(cart) {
1491
+ if (!Array.isArray(cart)) {
1492
+ console.error('Invalid cart data:', cart);
1493
+ return;
1494
+ }
1495
+
1496
+ let totalQuantity = 0;
1497
+ cart.forEach(item => {
1498
+ totalQuantity += item.quantity;
1499
+ });
1500
+
1501
+ const cartItemCount = document.getElementById('cart-item-count');
1502
+ if (cartItemCount) {
1503
+ cartItemCount.innerText = totalQuantity;
1504
+ cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
1505
+ }
1506
+
1507
+ const buttonContainers = document.querySelectorAll('.button-container');
1508
+ buttonContainers.forEach(container => {
1509
+ const itemName = container.getAttribute('data-item-name');
1510
+ const section = container.getAttribute('data-item-section');
1511
+ const quantityDisplay = container.querySelector('.quantity-display');
1512
+ const addButton = container.querySelector('.add-to-cart-btn');
1513
+ const quantitySelector = container.querySelector('.quantity-selector');
1514
+
1515
+ const cartItem = cart.find(item =>
1516
+ item.itemName === itemName &&
1517
+ item.section === section &&
1518
+ item.instructions === '' &&
1519
+ JSON.stringify(item.addons) === JSON.stringify([])
1520
+ );
1521
+
1522
+ if (cartItem && cartItem.quantity > 0) {
1523
+ quantityDisplay.innerText = cartItem.quantity;
1524
+ addButton.style.display = 'none';
1525
+ quantitySelector.style.display = 'flex';
1526
+ } else {
1527
+ quantityDisplay.innerText = '0';
1528
+ addButton.style.display = 'block';
1529
+ quantitySelector.style.display = 'none';
1530
+ }
1531
+ });
1532
  }
1533
 
1534
+ // Event handlers
1535
  function handleSoftDrinkAdd(button) {
1536
  const buttonContainer = button.closest('.button-container');
1537
  const quantitySelector = buttonContainer.querySelector('.quantity-selector');
 
1548
  }
1549
 
1550
  function increaseQuantity(button) {
 
 
 
1551
  const buttonContainer = button.closest('.button-container');
1552
  const quantityDisplay = buttonContainer.querySelector('.quantity-display');
1553
  let currentQuantity = parseInt(quantityDisplay.innerText) || 0;
 
1555
  if (currentQuantity < 10) {
1556
  const newQuantity = currentQuantity + 1;
1557
  quantityDisplay.innerText = newQuantity;
1558
+ updateCartQuantity(buttonContainer, newQuantity);
1559
  } else {
1560
+ showToast('Maximum quantity of 10 reached for Soft Drinks', 'error');
 
1561
  }
1562
  }
1563
 
1564
  function decreaseQuantity(button) {
 
 
 
1565
  const buttonContainer = button.closest('.button-container');
1566
  const quantityDisplay = buttonContainer.querySelector('.quantity-display');
1567
  const addButton = buttonContainer.querySelector('.add-to-cart-btn');
 
1576
  } else {
1577
  const newQuantity = currentQuantity - 1;
1578
  quantityDisplay.innerText = newQuantity;
1579
+ updateCartQuantity(buttonContainer, newQuantity);
1580
  }
1581
  }
1582
 
 
1599
  };
1600
 
1601
  if (newQuantity > 0) {
1602
+ enqueueCartOperation({
1603
+ url: '/cart/add',
1604
  method: 'POST',
1605
+ payload: cartPayload
1606
+ }).catch(err => {
1607
+ console.error('Error adding to cart:', err);
1608
+ showToast('Failed to update cart. Please try again.', 'error');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1609
  });
1610
  } else {
1611
+ enqueueCartOperation({
1612
+ url: `/cart/remove?item_name=${encodeURIComponent(itemName)}&instructions=&addons=[]`,
1613
+ method: 'POST'
1614
+ }).catch(err => {
1615
+ console.error('Error removing from cart:', err);
1616
+ showToast('Failed to update cart. Please try again.', 'error');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1617
  });
1618
  }
1619
  }
1620
 
1621
+ function showItemDetails(name, price, image, description, section, selectedCategory) {
1622
+ document.getElementById('modal-name').innerText = name;
1623
+ document.getElementById('modal-price').innerText = `$${price}`;
1624
+ const modalImg = document.getElementById('modal-img');
1625
+ modalImg.src = image || '/static/placeholder.jpg';
1626
+ modalImg.alt = name;
1627
+ document.getElementById('modal-description').innerText = description || 'No description available.';
1628
+ document.getElementById('addons-list').innerHTML = 'Loading customization options...';
1629
+ document.getElementById('modal-instructions').value = '';
1630
+ const modalSectionEl = document.getElementById('modal-section');
1631
+ modalSectionEl.setAttribute('data-section', section);
1632
+ modalSectionEl.setAttribute('data-category', selectedCategory);
1633
+ document.getElementById('quantityValue').innerText = '1';
 
 
 
 
 
 
 
 
 
 
 
 
 
1634
 
1635
+ fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
1636
+ .then(response => response.json())
1637
+ .then(data => {
1638
+ const addonsList = document.getElementById('addons-list');
1639
+ addonsList.innerHTML = '';
1640
+
1641
+ if (!data.success || !data.addons || data.addons.length === 0) {
1642
+ addonsList.innerHTML = '<p>No customization options available.</p>';
1643
+ return;
1644
+ }
1645
+
1646
+ data.addons.forEach(addon => {
1647
+ const sectionDiv = document.createElement('div');
1648
+ sectionDiv.classList.add('addon-section');
1649
+
1650
+ const title = document.createElement('h6');
1651
+ title.innerText = addon.name;
1652
+ sectionDiv.appendChild(title);
1653
+
1654
+ const optionsContainer = document.createElement('div');
1655
+ addon.options.forEach((option, index) => {
1656
+ const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
1657
+ const listItem = document.createElement('div');
1658
+ listItem.classList.add('form-check');
1659
+
1660
+ listItem.innerHTML = `
1661
+ <input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
1662
+ data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
1663
+ <label class="form-check-label" for="${optionId}">
1664
+ ${option} ${addon.extra_charge ? `($${addon.extra_charge_amount})` : ''}
1665
+ </label>
1666
+ `;
1667
+
1668
+ optionsContainer.appendChild(listItem);
1669
+ });
1670
+
1671
+ sectionDiv.appendChild(optionsContainer);
1672
+ addonsList.appendChild(sectionDiv);
1673
+ });
1674
+ })
1675
+ .catch(err => {
1676
+ console.error('Error fetching add-ons:', err);
1677
+ document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
1678
+ });
1679
+ }
1680
 
1681
+ function addToCartFromModal() {
1682
+ const itemName = document.getElementById('modal-name').innerText;
1683
+ let itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
1684
+
1685
+ if (isNaN(itemPrice)) {
1686
+ showToast('Invalid price for the item. Please check the item details.', 'error');
1687
+ return;
1688
+ }
1689
+
1690
+ const itemImage = document.getElementById('modal-img').src;
1691
+ const modalSectionEl = document.getElementById('modal-section');
1692
+ const section = modalSectionEl.getAttribute('data-section');
1693
+ const selectedCategory = modalSectionEl.getAttribute('data-category');
1694
+
1695
+ if (!itemName || !itemPrice || !section || !itemImage) {
1696
+ console.error('Missing data for cart item:', { itemName, itemPrice, section, itemImage });
1697
+ showToast('Missing item data. Please try again.', 'error');
1698
+ return;
1699
+ }
1700
+
1701
+ const selectedAddOns = Array.from(
1702
+ document.querySelectorAll('#addons-list input[type="checkbox"]:checked')
1703
+ ).map(addon => ({
1704
+ name: addon.getAttribute('data-name') || 'Default Name',
1705
+ price: parseFloat(addon.getAttribute('data-price') || 0)
1706
+ }));
1707
+
1708
+ const quantity = parseInt(document.getElementById('quantityValue').innerText) || 1;
1709
+ const instructions = document.getElementById('modal-instructions').value;
1710
+
1711
+ const cartPayload = {
1712
+ itemName: itemName,
1713
+ itemPrice: itemPrice,
1714
+ itemImage: itemImage,
1715
+ section: section,
1716
+ category: selectedCategory,
1717
+ addons: selectedAddOns,
1718
+ instructions: instructions,
1719
+ quantity: quantity
1720
+ };
1721
+
1722
+ enqueueCartOperation({
1723
+ url: '/cart/add',
1724
+ method: 'POST',
1725
+ payload: cartPayload
1726
+ })
1727
+ .then(() => {
1728
+ showToast('Item added to cart successfully!');
1729
+ const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
1730
+ modal.hide();
1731
+ })
1732
+ .catch(err => {
1733
+ console.error('Error adding to cart:', err);
1734
+ showToast('Failed to add item to cart. Please try again.', 'error');
1735
+ });
1736
+ }
1737
+
1738
+ // Search and filter functions
1739
+ function filterMenu() {
1740
+ const input = document.getElementById('searchBar').value.trim().toLowerCase();
1741
+ const sections = document.querySelectorAll('h3');
1742
+ const items = document.querySelectorAll('.menu-card');
1743
+ let matchedSections = new Set();
1744
+
1745
+ items.forEach(item => {
1746
+ const itemName = item.querySelector('.card-title').innerText.toLowerCase();
1747
+ const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
1748
+
1749
+ if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
1750
+ item.style.display = 'block';
1751
+ item.classList.add('visible');
1752
+ matchedSections.add(item.closest('.row'));
1753
  } else {
1754
+ item.style.display = 'none';
 
 
1755
  }
1756
  });
1757
+
1758
+ sections.forEach(section => {
1759
+ const sectionRow = section.nextElementSibling;
1760
+
1761
+ if (matchedSections.has(sectionRow)) {
1762
+ section.style.display = 'block';
1763
+ sectionRow.style.display = 'flex';
1764
+ } else {
1765
+ section.style.display = 'none';
1766
+ sectionRow.style.display = 'none';
1767
+ }
1768
+ });
1769
+
1770
+ if (!input) {
1771
+ sections.forEach(section => {
1772
+ section.style.display = 'block';
1773
+ section.nextElementSibling.style.display = 'flex';
1774
+ });
1775
+
1776
+ items.forEach(item => {
1777
+ item.style.display = 'block';
1778
+ item.classList.add('visible');
1779
+ });
1780
+ }
1781
  }
1782
 
1783
+ // Initialize the page
1784
  document.addEventListener('DOMContentLoaded', function () {
1785
+ // Initialize video observers
1786
+ initializeVideoObservers();
1787
+
1788
+ // Setup avatar dropdown
1789
  const avatarContainer = document.querySelector('.avatar-dropdown-container');
1790
  const dropdownMenu = document.querySelector('.dropdown-menu');
1791
+
1792
  avatarContainer.addEventListener('click', function (event) {
1793
  event.stopPropagation();
1794
  dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1795
  });
1796
+
1797
  document.addEventListener('click', function (event) {
1798
  if (!avatarContainer.contains(event.target)) {
1799
  dropdownMenu.style.display = 'none';
1800
  }
1801
  });
1802
+
1803
+ // Setup dropdown items
1804
  const dropdownItems = document.querySelectorAll('.dropdown-item');
1805
  dropdownItems.forEach(item => {
1806
  item.addEventListener('click', function () {
1807
  dropdownMenu.style.display = 'none';
1808
  });
1809
  });
1810
+
1811
+ // Setup menu card details
1812
  const menuCards = document.querySelectorAll('.menu-card');
1813
  menuCards.forEach(card => {
1814
  const itemName = card.querySelector('.card-title').innerText;
1815
  const detailsDiv = card.querySelector('.item-details');
1816
  const detailsData = menuItemDetails[itemName];
1817
+
1818
  if (detailsData) {
1819
  detailsDiv.innerHTML = `
1820
  <h6>Ingredients</h6>
 
1833
  detailsDiv.innerHTML = '<p>No details available for this item.</p>';
1834
  }
1835
  });
1836
+
1837
+ // Setup card visibility observer
1838
  const cardObserver = new IntersectionObserver((entries, observer) => {
1839
  entries.forEach(entry => {
1840
  if (entry.isIntersecting) {
 
1847
  rootMargin: '0px',
1848
  threshold: 0.1
1849
  });
1850
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1851
  menuCards.forEach(card => cardObserver.observe(card));
1852
+
1853
+ // Setup toggle details buttons
1854
  const toggleLinks = document.querySelectorAll('.toggle-details');
1855
  toggleLinks.forEach(link => {
1856
  link.addEventListener('click', function () {
1857
  const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1858
  const detailsDiv = document.getElementById(`details-${itemName}`);
1859
+ const isExpanded = detailsDiv.classList.toggle('show');
1860
+
1861
+ this.innerText = isExpanded ? 'Hide Details' : 'Show Details';
1862
+ this.setAttribute('aria-expanded', isExpanded);
1863
  });
1864
  });
1865
+
1866
+ // Setup category buttons
1867
  const categoryButtons = document.querySelectorAll('.category-button');
1868
  const categoryForm = document.getElementById('categoryForm');
1869
  const selectedCategoryInput = document.getElementById('selectedCategoryInput');
1870
+
1871
  if (!selectedCategoryInput.value) {
1872
  selectedCategoryInput.value = "All";
1873
  document.querySelector('.category-button[data-category="All"]').classList.add('selected');
1874
  }
1875
+
1876
  categoryButtons.forEach(button => {
1877
  button.addEventListener('click', function () {
1878
+ categoryButtons.forEach(btn => {
1879
+ btn.classList.remove('selected');
1880
+ btn.setAttribute('aria-pressed', 'false');
1881
+ });
1882
+
1883
  this.classList.add('selected');
1884
+ this.setAttribute('aria-pressed', 'true');
1885
  selectedCategoryInput.value = this.getAttribute('data-category');
1886
  categoryForm.submit();
1887
  });
1888
  });
1889
+
1890
+ // Setup search functionality
1891
  const searchBar = document.getElementById('searchBar');
1892
  const suggestionsContainer = document.getElementById('autocompleteSuggestions');
1893
+
1894
  searchBar.addEventListener('input', function () {
1895
  const input = this.value.trim().toLowerCase();
1896
  suggestionsContainer.innerHTML = '';
1897
  suggestionsContainer.style.display = 'none';
1898
+
1899
  if (input) {
1900
  const filteredItems = menuItems.filter(item =>
1901
  item.toLowerCase().includes(input)
1902
  );
1903
+
1904
  if (filteredItems.length > 0) {
1905
  filteredItems.forEach(item => {
1906
  const suggestionDiv = document.createElement('div');
1907
  suggestionDiv.classList.add('suggestion-item');
1908
  suggestionDiv.innerText = item;
1909
+ suggestionDiv.setAttribute('role', 'option');
1910
+
1911
  suggestionDiv.addEventListener('click', function () {
1912
  searchBar.value = item;
1913
  suggestionsContainer.style.display = 'none';
1914
  filterMenu();
1915
  });
1916
+
1917
  suggestionsContainer.appendChild(suggestionDiv);
1918
  });
1919
+
1920
  suggestionsContainer.style.display = 'block';
1921
  }
1922
  }
1923
+
1924
  filterMenu();
1925
  });
1926
+
1927
  document.addEventListener('click', function (event) {
1928
  if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
1929
  suggestionsContainer.style.display = 'none';
1930
  }
1931
  });
1932
+
1933
+ // Setup description textarea suggestions
1934
  const descriptionTextarea = document.getElementById('custom-dish-description');
1935
  const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1936
+
1937
  if (descriptionTextarea && descriptionSuggestions) {
1938
  let usedIngredients = new Set();
1939
+
1940
  function updateUsedIngredients() {
1941
  const inputText = descriptionTextarea.value.trim();
1942
  usedIngredients.clear();
1943
+
1944
  if (inputText) {
1945
  const words = inputText.split(/,\s*/).map(word => word.trim());
1946
  words.forEach(word => {
 
1950
  });
1951
  }
1952
  }
1953
+
1954
  descriptionTextarea.addEventListener('input', function () {
1955
  const inputText = this.value.trim();
1956
  const words = inputText.split(/,\s*/);
1957
  const lastWord = words[words.length - 1].trim().toLowerCase();
1958
+
1959
  descriptionSuggestions.innerHTML = '';
1960
  descriptionSuggestions.style.display = 'none';
1961
  updateUsedIngredients();
1962
+
1963
  if (lastWord) {
1964
  const filteredIngredients = ingredientsList.filter(ingredient =>
1965
  ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
1966
  );
1967
+
1968
  if (filteredIngredients.length > 0) {
1969
  filteredIngredients.forEach(ingredient => {
1970
  const suggestionDiv = document.createElement('div');
1971
  suggestionDiv.classList.add('suggestion-item');
1972
  suggestionDiv.innerText = ingredient;
1973
+ suggestionDiv.setAttribute('role', 'option');
1974
+
1975
  suggestionDiv.addEventListener('click', function () {
1976
  const currentValue = descriptionTextarea.value;
1977
  const lastCommaIndex = currentValue.lastIndexOf(',');
 
1981
  descriptionTextarea.focus();
1982
  updateUsedIngredients();
1983
  });
1984
+
1985
  descriptionSuggestions.appendChild(suggestionDiv);
1986
  });
1987
+
1988
  descriptionSuggestions.style.display = 'block';
1989
  }
1990
  }
1991
  });
1992
+
1993
  document.addEventListener('click', function (event) {
1994
  if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1995
  descriptionSuggestions.style.display = 'none';
1996
  }
1997
  });
1998
  }
1999
+
2000
+ // Load cart data
2001
  fetch('/cart/get')
2002
  .then(response => {
2003
  if (!response.ok) {
 
2019
  const cart = getCartLocalStorage();
2020
  updateCartUI(cart);
2021
  });
2022
+
2023
+ // Setup modal quantity controls
 
 
 
 
2024
  const decreaseBtn = document.getElementById('decreaseQuantity');
2025
  const increaseBtn = document.getElementById('increaseQuantity');
2026
+ const quantityValue = document.getElementById('quantityValue');
2027
+
2028
  decreaseBtn.addEventListener('click', function () {
2029
+ let currentQuantity = parseInt(quantityValue.innerText);
2030
  if (currentQuantity > 1) {
2031
  currentQuantity--;
2032
+ quantityValue.innerText = currentQuantity;
2033
  }
2034
  });
2035
+
2036
  increaseBtn.addEventListener('click', function () {
2037
+ let currentQuantity = parseInt(quantityValue.innerText);
2038
  currentQuantity++;
2039
+ quantityValue.innerText = currentQuantity;
2040
  });
2041
+
2042
+ // Setup voice search
2043
  const micIcon = document.getElementById('micIcon');
2044
+
2045
  if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
2046
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
2047
  const recognition = new SpeechRecognition();
2048
  recognition.lang = 'en-US';
2049
+
2050
+ recognition.onstart = () => {
2051
+ micIcon.classList.add('active');
2052
+ showToast('Listening... Speak now', 'info');
2053
+ };
2054
+
2055
  recognition.onresult = (event) => {
2056
+ const transcript = event.results[0][0].transcript.trim();
2057
+ searchBar.value = transcript;
2058
  filterMenu();
2059
+ showToast(`Searching for: ${transcript}`, 'info');
2060
  };
2061
+
2062
  recognition.onend = () => micIcon.classList.remove('active');
2063
  recognition.onerror = (event) => {
2064
  micIcon.classList.remove('active');
2065
  console.error('Speech error:', event.error);
2066
+ showToast('Voice recognition error. Please try again.', 'error');
2067
  };
2068
+
2069
  micIcon.addEventListener('click', () => {
2070
+ try {
2071
+ recognition.start();
2072
+ } catch (err) {
2073
+ console.error('Speech recognition error:', err);
2074
+ showToast('Voice search not available. Please try again.', 'error');
2075
+ }
2076
  });
2077
  } else {
2078
  micIcon.style.display = 'none';
2079
  }
2080
+
2081
+ // Setup video hover events
2082
+ document.querySelectorAll('.video-container').forEach(container => {
2083
+ container.addEventListener('mouseenter', () => handleVideoHover(container, true));
2084
+ container.addEventListener('mouseleave', () => handleVideoHover(container, false));
2085
+ container.addEventListener('focus', () => handleVideoHover(container, true));
2086
+ container.addEventListener('blur', () => handleVideoHover(container, false));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2087
  });
2088
+
2089
+ // Setup addon selection handling
2090
+ document.addEventListener('click', function(event) {
2091
+ if (event.target.classList.contains('addon-option')) {
2092
+ const checkbox = event.target;
2093
+ const groupName = checkbox.getAttribute('data-group');
2094
+ const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it a Combo"].includes(groupName);
2095
+
2096
+ if (!isMultiSelectGroup) {
2097
+ const checkboxes = document.querySelectorAll(`.addon-option[data-group="${groupName}"]`);
2098
+ checkboxes.forEach(otherCheckbox => {
2099
+ if (otherCheckbox !== checkbox) {
2100
+ otherCheckbox.checked = false;
2101
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2102
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2103
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2104
  }
 
 
 
 
 
 
 
 
2105
  });
2106
+ });
2107
  </script>
2108
  </body>
2109
  </html>