Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta http-equiv="x-ua-compatible" content="ie=edge"> | |
| <title>Order Summary</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| /* Custom animations */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes lightMoveUp { | |
| 0% { background-position: 0 100%; } | |
| 100% { background-position: 0 0%; } | |
| } | |
| @keyframes shine { | |
| from { transform: rotate(0deg) translateX(-100%); } | |
| to { transform: rotate(25deg) translateX(100%); } | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(255, 179, 71, 0.7); } | |
| 70% { box-shadow: 0 0 0 10px rgba(255, 179, 71, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(255, 179, 71, 0); } | |
| } | |
| @keyframes slideDown { | |
| from { max-height: 0; opacity: 0; } | |
| to { max-height: 1000px; opacity: 1; } | |
| } | |
| .modal, .popup { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| .progress-bar { | |
| transition: width 0.6s ease-in-out, background 0.3s ease-in-out; | |
| } | |
| /* Progress bar color ranges */ | |
| .progress-bar.range-0-100 { | |
| background: linear-gradient(90deg, #2DD4BF, #5EEAD4); | |
| } | |
| .progress-bar.range-100-200 { | |
| background: linear-gradient(90deg, #10B981, #34D399); | |
| } | |
| .progress-bar.range-200-plus { | |
| background: linear-gradient(90deg, #F59E0B, #FBBF24); | |
| } | |
| .ingredient-button:hover .ingredient-image { | |
| transform: scale(1.1); | |
| border-color: #10B981; | |
| } | |
| .tier-badge { | |
| position: relative; | |
| overflow: hidden; | |
| background-color: #FFB347; | |
| animation: pulse 2s infinite; | |
| will-change: box-shadow; | |
| } | |
| .tier-badge::after { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| left: -50%; | |
| width: 200%; | |
| height: 200%; | |
| background: linear-gradient(45deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%); | |
| animation: shine 3s infinite; | |
| } | |
| /* Custom class for order items */ | |
| .custom-class { | |
| text-align: center; | |
| border-radius: 12px; | |
| border-width: 1px; | |
| border-color: #E5E7EB; | |
| padding: 24px ; | |
| margin-bottom: 16px; | |
| background-color: #FFFFFF; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | |
| transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; | |
| width: 100%; | |
| max-width: clamp(288px, 100%, 488px); | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .custom-class:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Order image wrapper for positioning text */ | |
| .order-image-wrapper { | |
| position: relative; | |
| display: block; | |
| margin-bottom: 16px; | |
| } | |
| /* Order image styles */ | |
| .order-image { | |
| max-width: 100%; | |
| max-height: 330px; | |
| width: auto; | |
| height: auto; | |
| aspect-ratio: 4/3; | |
| object-fit: contain; | |
| border-radius: 10px; | |
| border: 3px solid #D1D5DB; | |
| box-sizing: border-box; | |
| box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); | |
| transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; | |
| will-change: transform, box-shadow; | |
| display: block; | |
| margin: 0 auto 20px; | |
| } | |
| .order-image:hover { | |
| transform: scale(1.04); | |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); | |
| } | |
| /* Fallback for placeholder images */ | |
| .order-image[src$="placeholder.jpg"] { | |
| background: linear-gradient(135deg, #F9FAFB, #E5E7EB); | |
| object-fit: contain; | |
| } | |
| /* Item name below image */ | |
| .order-item-name { | |
| display: block; | |
| text-align: center; | |
| color: #000000; | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-top: 12px; | |
| padding: 8px 16px; | |
| background: #FFFFFF; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| max-width: 100%; | |
| transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; | |
| } | |
| .order-image-wrapper:hover .order-item-name { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| } | |
| .history { | |
| display: flex; | |
| justify-content: center; | |
| font-size: 1.75rem; | |
| font-weight: 700; | |
| padding: 1.5rem; | |
| background: linear-gradient(45deg, #FFF7ED, #FFE4D6); | |
| border-radius: 8px; | |
| margin-bottom: 1rem; | |
| } | |
| /* Scrollable sector images */ | |
| .sector-images-container { | |
| position: relative; | |
| display: flex; | |
| flex-wrap: nowrap; | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| padding: 0 10px; | |
| gap: 20px; | |
| width: 100%; | |
| max-width: none; | |
| scroll-behavior: smooth; | |
| scrollbar-width: thin; | |
| scrollbar-color: #FFB347 #E5E7EB; | |
| } | |
| .sector-images-container::-webkit-scrollbar { | |
| height: 8px; | |
| } | |
| .sector-images-container::-webkit-scrollbar-track { | |
| background: #E5E7EB; | |
| } | |
| .sector-images-container::-webkit-scrollbar-thumb { | |
| background: #FFB347; | |
| border-radius: 4px; | |
| } | |
| .sector-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| margin: 0 auto; | |
| flex-shrink: 0; | |
| background: #FFF7ED; | |
| border-radius: 8px; | |
| padding: 8px; | |
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); | |
| transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; | |
| } | |
| .sector-item:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| } | |
| .sector-image { | |
| max-width: 150px; | |
| max-height: 100px; | |
| width: auto; | |
| height: auto; | |
| aspect-ratio: 3/2; | |
| object-fit: contain; | |
| cursor: pointer; | |
| border-radius: 8px; | |
| border: 1px solid #D1D5DB; | |
| box-sizing: border-box; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |
| transition: transform 0.15s ease-in-out; | |
| will-change: transform; | |
| } | |
| .sector-image:hover { | |
| transform: scale(1.08); | |
| } | |
| .sector-name { | |
| text-align: center; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| margin-top: 6px; | |
| max-width: 150px; | |
| color: #1F2937; | |
| } | |
| /* Scroll buttons */ | |
| .scroll-button { | |
| position: absolute; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| background: #FFB347; | |
| color: #FFFFFF; | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 20px; | |
| cursor: pointer; | |
| opacity: 0; | |
| transition: opacity 0.3s ease-in-out, transform 0.2s ease-in-out; | |
| z-index: 10; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .scroll-button:hover { | |
| background: #FF9E2C; | |
| transform: translateY(-50%) scale(1.1); | |
| } | |
| .sector-images-container:hover .scroll-button { | |
| opacity: 1; | |
| } | |
| .scroll-button.left { | |
| left: 0; | |
| } | |
| .scroll-button.right { | |
| right: 0; | |
| } | |
| /* Full-width gradient header */ | |
| .back-to-menu { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| display: flex; | |
| align-items: center; | |
| padding: 14px 24px; | |
| background: linear-gradient(45deg, #FFA07A, #FFB347); | |
| color: #FFFFFF; | |
| font-size: 1.125rem; | |
| font-weight: 600; | |
| text-decoration: none; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
| z-index: 9999; | |
| transition: background-color 0.3s ease, transform 0.2s ease; | |
| } | |
| .back-to-menu:hover { | |
| background: linear-gradient(45deg, #FF8C61, #FF9E2C); | |
| transform: translateY(-1px); | |
| } | |
| /* Content spacing */ | |
| .container { | |
| margin-top: 90px; | |
| max-width: 100%; | |
| padding: 20px; | |
| } | |
| /* Sector popup */ | |
| #sector-popup { | |
| display: none; | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(17, 24, 39, 0.6); | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 10000; | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| #popup-content { | |
| background: linear-gradient(135deg, #FFFFFF 0%, #F9FAFB 100%); | |
| padding: 24px; | |
| border-radius: 12px; | |
| width: 90%; | |
| max-width: 550px; | |
| max-height: 85vh; | |
| overflow-y: auto; | |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | |
| border: 1px solid #E5E7EB; | |
| position: relative; | |
| contain: layout; | |
| } | |
| .popup-header { | |
| position: relative; | |
| padding-bottom: 12px; | |
| margin-bottom: 16px; | |
| border-bottom: 2px solid #FFB347; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .popup-close { | |
| background: #FFB347; | |
| color: #FFFFFF; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| font-weight: bold; | |
| border: none; | |
| cursor: pointer; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| line-height: 1; | |
| padding: 0; | |
| position: relative; | |
| overflow: hidden; | |
| will-change: transform, background; | |
| } | |
| .popup-close::before { | |
| content: ''; | |
| position: absolute; | |
| top: 100%; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(to top, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%); | |
| animation: lightMoveUp 1.5s infinite linear; | |
| z-index: 0; | |
| } | |
| .popup-close:hover { | |
| background: #FF9E2C; | |
| transform: scale(1.1); | |
| } | |
| .popup-close span { | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* Ingredient modal */ | |
| .ingredient-modal-container { | |
| display: none; | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.6); | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 10000; | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| .ingredient-modal { | |
| background: linear-gradient(135deg, #FFFFFF 0%, #F9FAFB 100%); | |
| border-radius: 12px; | |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); | |
| border: 1px solid #E5E7EB; | |
| width: 90%; | |
| max-width: 500px; | |
| max-height: 90vh; | |
| overflow-y: auto; | |
| position: relative; | |
| contain: layout; | |
| } | |
| .modal-section { | |
| padding: 16px; | |
| border-left: 4px solid #2DD4BF; | |
| margin-bottom: 16px; | |
| background: #F9FAFB; | |
| border-radius: 6px; | |
| } | |
| /* Ingredient images */ | |
| .ingredient-image { | |
| max-width: 100%; | |
| max-height: 100px; | |
| width: 100%; | |
| height: 100px; | |
| aspect-ratio: 1/1; | |
| object-fit: cover; | |
| border-radius: 6px; | |
| border: 1px solid #D1D5DB; | |
| transition: transform 0.3s ease-in-out, border-color 0.3s ease-in-out; | |
| } | |
| /* Hover effect for ingredient buttons */ | |
| .ingredient-button:hover .ingredient-image { | |
| transform: scale(1.1); | |
| border-color: #10B981; | |
| } | |
| /* Green border when section is visible */ | |
| .ingredients-section:not(.hidden) .ingredient-image { | |
| border: 2px solid #10B981; | |
| } | |
| /* Ensure ingredient section is smooth */ | |
| .ingredients-section { | |
| transition: opacity 0.3s ease-in-out, max-height 0.3s ease-in-out; | |
| max-height: 0; | |
| opacity: 0; | |
| overflow: hidden; | |
| } | |
| .ingredients-section:not(.hidden) { | |
| max-height: 1000px; | |
| opacity: 1; | |
| } | |
| /* Prevent body scroll when modal is open */ | |
| body.modal-open { | |
| overflow: hidden; | |
| } | |
| /* Fallback messages */ | |
| .no-data { | |
| text-align: center; | |
| color: #6B7280; | |
| font-size: 1.125rem; | |
| padding: 20px; | |
| background: #F9FAFB; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| /* Order confirmed section */ | |
| .order-confirmed { | |
| background: linear-gradient(135deg, #FFF7ED 0%, #FFE4D6 100%); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .order-confirmed::before { | |
| content: '✔'; | |
| position: absolute; | |
| top: 50; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 120px; | |
| color: rgba(16, 185, 129, 0.1); | |
| z-index: 0; | |
| } | |
| .order-confirmed h1 { | |
| font-size: 2.25rem; | |
| z-index: 1; | |
| position: relative; | |
| } | |
| /* Success message styling */ | |
| .success-message { | |
| text-align: center; | |
| color: #10B981; | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-top: 1rem; | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| /* Timer styling */ | |
| .delivery-timer { | |
| color: #F59E0B; | |
| font-weight: 600; | |
| } | |
| /* Tier details animation */ | |
| .tier-details { | |
| overflow: hidden; | |
| transition: max-height 0.5s ease-in-out, opacity 0.3s ease-in-out; | |
| max-height: 0; | |
| opacity: 0; | |
| } | |
| .tier-details:not(.hidden) { | |
| max-height: 1000px; | |
| opacity: 1; | |
| animation: slideDown 0.5s ease-in-out; | |
| } | |
| /* Price badge */ | |
| .price-badge { | |
| display: inline-block; | |
| background: #FFE4D6; | |
| padding: 6px 14px; | |
| border-radius: 14px; | |
| font-weight: 600; | |
| color: #C2410C; | |
| } | |
| /* Show Ingredients button focus style */ | |
| .ingredients-toggle-button:focus { | |
| outline: none; | |
| transform: scale(1.05); | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .custom-class { | |
| padding: 20px ; | |
| max-width: 400px; | |
| } | |
| .order-image { | |
| max-height: 270px; | |
| } | |
| .order-item-name { | |
| font-size: 1.25rem; | |
| padding: 6px 12px; | |
| } | |
| .grid { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .custom-class { | |
| max-width: 336px; | |
| } | |
| .order-image { | |
| max-height: 225px; | |
| } | |
| .order-item-name { | |
| font-size: 1.125rem; | |
| } | |
| } | |
| @media (max-width: 360px) { | |
| .custom-class { | |
| max-width: 296px; | |
| } | |
| .order-image { | |
| max-height: 195px; | |
| } | |
| .order-item-name { | |
| font-size: 1rem; | |
| padding: 4px 10px; | |
| } | |
| } | |
| @media (max-width: 640px) { | |
| .back-to-menu { | |
| padding: 12px 16px; | |
| font-size: 1rem; | |
| } | |
| .container { | |
| margin-top: 70px; | |
| padding: 16px; | |
| } | |
| .history { | |
| font-size: 1.5rem; | |
| padding: 1rem; | |
| } | |
| .sector-image { | |
| max-width: 120px; | |
| max-height: 80px; | |
| } | |
| .sector-name { | |
| max-width: 120px; | |
| } | |
| .sector-item { | |
| padding: 6px; | |
| } | |
| .order-confirmed h1 { | |
| font-size: 1.75rem; | |
| } | |
| .scroll-button { | |
| width: 32px; | |
| height: 32px; | |
| font-size: 16px; | |
| } | |
| #popup-content { | |
| width: 95%; | |
| padding: 16px; | |
| max-height: 80vh; | |
| } | |
| .ingredient-modal { | |
| width: 95%; | |
| max-height: 80vh; | |
| } | |
| .popup-close { | |
| width: 40px; | |
| height: 40px; | |
| font-size: 24px; | |
| } | |
| .popup-close::before { | |
| animation: lightMoveUp 1.2s infinite linear; | |
| } | |
| .ingredient-image { | |
| max-height: 80px; | |
| height: 80px; | |
| } | |
| .grid { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .success-message { | |
| font-size: 1.25rem; | |
| } | |
| } | |
| @media (min-width: 768px) { | |
| .grid { | |
| grid-template-columns: repeat(3, 1fr); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <a href="{{ url_for('menu.menu') }}" class="back-to-menu" aria-label="Go back to menu"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M15 18l-6-6 6-6"></path> | |
| </svg> | |
| Back to Menu | |
| </a> | |
| <div class="container mx-auto p-6 pt-2"> | |
| <!-- Order Confirmation --> | |
| <div class="section bg-white shadow-sm rounded-xl p-6 mb-6 order-confirmed" role="alert"> | |
| <h1 class="text-center text-2xl font-bold text-orange-500">Order Confirmed!</h1> | |
| <p class="text-center text-gray-600 mt-2"> | |
| Estimated delivery time: <span id="deliveryTimer" class="delivery-timer">{{ delivery_time }}:00</span> | |
| </p> | |
| <p id="successMessage" class="success-message hidden">Successfully Delivered!</p> | |
| </div> | |
| <!-- Reward Status Section --> | |
| <div class="section bg-white rounded-xl shadow-lg p-6 mb-6"> | |
| <div class="flex items-center justify-between mb-4 cursor-pointer" onclick="toggleTierDetails()" aria-expanded="false" aria-controls="tierDetails"> | |
| <div id="tierBadge" class="w-8 h-8 p-1 rounded-full flex items-center justify-center tier-badge"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-white"> | |
| <path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path> | |
| <path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path> | |
| <path d="M4 22h16"></path> | |
| <path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"></path> | |
| <path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"></path> | |
| <path d="M9 9c0 .97.64 1.79 1.5 2.05A2 2 0 0 1 12 13c.82 0 1.54-.39 2-1"></path> | |
| <path d="M18 9H6a4 4 0 0 1 0-8h12a4 4 0 0 1 0 8Z"></path> | |
| </svg> | |
| </div> | |
| <span class="ml-2 text-xl font-bold text-orange-500" id="currentTier">{{ current_tier }} Tier</span> | |
| <svg id="arrowIcon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-600"> | |
| <path id="arrowPath" d="M19 9l-7 7-7-7"></path> | |
| </svg> | |
| </div> | |
| <!-- Collapsible Content Section --> | |
| <div id="tierDetails" class="tier-details hidden"> | |
| <div class="text-center mb-6"> | |
| <p class="text-gray-600">Valid through December {{ validity_year }}</p> | |
| <p class="text-lg font-semibold mt-2 text-yellow-600" id="pointsDisplay">{{ user_points }} points</p> | |
| </div> | |
| <!-- Progress Bar --> | |
| <div class="relative h-4 bg-gray-200 rounded-full overflow-hidden mb-4"> | |
| <div id="progressBar" class="absolute left-0 top-0 h-full progress-bar" style="width: {{ progress_percentage }}%"></div> | |
| </div> | |
| <div class="flex justify-between text-sm text-gray-600 mb-4"> | |
| <span id="startPoint">{{ start_point }}</span> | |
| <span id="endPoint">{{ end_point }}</span> | |
| </div> | |
| <p class="text-center text-gray-700"> | |
| You need <span class="font-semibold text-gray-800" id="pointsNeeded">{{ points_needed_for_next_tier }}</span> more | |
| points to reach | |
| <span class="font-semibold text-orange-500" id="nextTier">{{ next_tier }}</span> | |
| </p> | |
| <!-- Tier Benefits --> | |
| <div class="mt-6 bg-gray-50 rounded-xl p-4"> | |
| <h3 class="text-lg font-semibold mb-4 text-gray-800" id="benefitsTitle"> | |
| Benefits of Reaching {{ next_tier }} Tier | |
| </h3> | |
| <ul class="space-y-3" id="benefitsList"> | |
| {% if next_tier == "Silver" %} | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🏷️</span> | |
| <span class="text-gray-700">10% discount on next purchase</span> | |
| </li> | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🍹</span> | |
| <span class="text-gray-700">Free soft drink with your next order</span> | |
| </li> | |
| {% elif next_tier == "Gold" %} | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🏷️</span> | |
| <span class="text-gray-700">15% discount on next purchase</span> | |
| </li> | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🍰</span> | |
| <span class="text-gray-700">Free dessert on next order</span> | |
| </li> | |
| {% elif next_tier == "Platinum" %} | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🏷️</span> | |
| <span class="text-gray-700">20% discount on next purchase</span> | |
| </li> | |
| <li class="flex items-center"> | |
| <span class="text-2xl mr-3">🍴</span> | |
| <span class="text-gray-700">Free entrée with your next order</span> | |
| </li> | |
| {% else %} | |
| <li class="text-gray-700">You've reached the highest tier! Enjoy exclusive benefits.</li> | |
| {% endif %} | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Ingredient History --> | |
| <div class="history"> | |
| <h1 class="text-center text-2xl font-bold text-orange-500">Ingredient History</h1> | |
| </div> | |
| {% if sector_details %} | |
| <div class="sector-images-container" role="region" aria-label="Ingredient History Carousel"> | |
| <button class="scroll-button left" onclick="scrollContainer('left')" aria-label="Scroll left"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M15 18l-6-6 6-6"></path> | |
| </svg> | |
| </button> | |
| {% for sector_name, sector in sector_details.items() %} | |
| <div class="sector-item"> | |
| <img | |
| src="{{ sector.image_url | default('/static/placeholder.jpg') }}" | |
| alt="{{ sector_name }}" | |
| class="sector-image lazyload" | |
| loading="lazy" | |
| onclick="showSectorDescription('{{ sector.description | escape }}')" | |
| role="img" | |
| aria-describedby="sector-name-{{ loop.index }}" | |
| onerror="this.src='/static/placeholder.jpg'; this.alt='Placeholder image'" | |
| > | |
| <h3 id="sector-name-{{ loop.index }}" class="sector-name">{{ sector_name }}</h3> | |
| </div> | |
| {% endfor %} | |
| <button class="scroll-button right" onclick="scrollContainer('right')" aria-label="Scroll right"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M9 18l6-6-6-6"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| {% else %} | |
| <div class="no-data">No sector details available.</div> | |
| {% endif %} | |
| <!-- Popup for Sector Description --> | |
| <div id="sector-popup" class="popup" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="sector-popup-title"> | |
| <div id="popup-content"> | |
| <div class="popup-header flex justify-between items-center"> | |
| <h3 id="sector-popup-title" class="text-xl font-semibold text-gray-800">Origin History</h3> | |
| <button onclick="closePopup()" class="popup-close" aria-label="Close sector description popup"><span>×</span></button> | |
| </div> | |
| <p id="sector-description" class="text-gray-700 leading-relaxed"></p> | |
| </div> | |
| </div> | |
| <!-- Order Items Section --> | |
| <div class="text-center text-2xl font-bold text-orange-500 mb-6">Previous Orders</div> | |
| {% if order_items %} | |
| {% for item in order_items %} | |
| <div class="custom-class"> | |
| <div class="order-image-wrapper"> | |
| <img | |
| src="{{ item.image_url | default('/static/placeholder.jpg') }}" | |
| alt="{{ item.name | escape }}" | |
| class="order-image lazyload" | |
| loading="lazy" | |
| decoding="async" | |
| role="img" | |
| aria-label="Image of {{ item.name | escape }}" | |
| aria-describedby="order-price-{{ loop.index }}" | |
| onerror="this.src='/static/placeholder.jpg'; this.alt='Placeholder image'" | |
| /> | |
| <h3 class="order-item-name" id="order-name-{{ loop.index }}">{{ item.name | escape }}</h3> | |
| </div> | |
| <p id="order-price-{{ loop.index }}" class="text-gray-600 mb-2 price-badge">${{ "%.2f"|format(item.price | default(0)) }}</p> | |
| {% if item.category != 'Soft Drink' %} | |
| <button | |
| id="ingredientsToggleButton{{ loop.index }}" | |
| class="ingredients-toggle-button text-green-600 text-sm font-semibold mt-2 ml-2 text-left pb-4 hover:text-green-700" | |
| onclick="toggleIngredients({{ loop.index }})" | |
| aria-expanded="false" | |
| aria-controls="ingredientsSection{{ loop.index }}" | |
| data-index="{{ loop.index }}" | |
| > | |
| Show Ingredients | |
| </button> | |
| <div id="ingredientsSection{{ loop.index }}" class="ingredients-section hidden mt-4" aria-hidden="true"> | |
| {% if item.ingredients and item.ingredients|length > 0 %} | |
| <div class="grid grid-cols-2 gap-4 sm:grid-cols-3"> | |
| {% for ingredient in item.ingredients %} | |
| <button | |
| class="relative group ingredient-button focus:outline-none focus:ring-2 focus:ring-green-500" | |
| aria-label="View details for {{ ingredient.name | escape }}" | |
| data-ingredient='{ | |
| "index": "{{ loop.index }}", | |
| "name": "{{ ingredient.name | escape }}", | |
| "image": "{{ ingredient.image | default('/static/placeholder.jpg') | escape }}", | |
| "health_benefits": "{{ ingredient.health_benefits | escape }}", | |
| "fun_facts": "{{ ingredient.fun_facts | escape }}" | |
| }' | |
| onclick="showIngredientDetails(this)" | |
| > | |
| <div class="overflow-hidden rounded-lg"> | |
| <img | |
| src="{{ ingredient.image | default('/static/placeholder.jpg') }}" | |
| alt="{{ ingredient.name | escape }}" | |
| class="ingredient-image lazyload" | |
| loading="lazy" | |
| decoding="async" | |
| onerror="this.src='/static/placeholder.jpg'; this.alt='Placeholder image'" | |
| /> | |
| </div> | |
| <p class="mt-2 text-center text-sm font-medium text-gray-800">{{ ingredient.name | escape }}</p> | |
| </button> | |
| {% endfor %} | |
| </div> | |
| {% else %} | |
| <p class="text-gray-600 text-center">No ingredients available for this item.</p> | |
| {% endif %} | |
| </div> | |
| {% endif %} | |
| </div> | |
| <!-- Modal for Ingredients --> | |
| {% if item.category != 'Soft Drink' %} | |
| <div id="ingredientModal{{ loop.index }}" class="ingredient-modal-container hidden modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle{{ loop.index }}"> | |
| <div class="ingredient-modal"> | |
| <div class="p-6"> | |
| <div class="popup-header flex justify-between items-center mb-4"> | |
| <h3 id="modalTitle{{ loop.index }}" class="text-xl font-semibold text-gray-800"></h3> | |
| <button onclick="closeModal({{ loop.index }})" class="popup-close" aria-label="Close ingredient details modal"><span>×</span></button> | |
| </div> | |
| <img id="modalImage{{ loop.index }}" src="" alt="" class="w-full max-h-48 object-contain rounded-xl mb-4 border border-gray-200 lazyload" loading="lazy" decoding="async" /> | |
| <div class="space-y-4"> | |
| <div class="modal-section"> | |
| <h4 class="font-semibold mb-2 text-gray-800 flex items-center"> | |
| <svg class="w-5 h-5 mr-2 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |
| </svg> | |
| Health Benefits | |
| </h4> | |
| <ul id="healthBenefits{{ loop.index }}" class="list-disc list-inside text-gray-700"></ul> | |
| </div> | |
| <div class="modal-section"> | |
| <h4 class="font-semibold mb-2 text-gray-800 flex items-center"> | |
| <svg class="w-5 h-5 mr-2 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |
| </svg> | |
| Fun Facts | |
| </h4> | |
| <ul id="funFacts{{ loop.index }}" class="list-disc list-inside text-gray-700"></ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endif %} | |
| {% endfor %} | |
| {% else %} | |
| <div class="no-data">No previous orders found.</div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| // Debounce function for performance | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| // Scroll container function | |
| function scrollContainer(direction) { | |
| try { | |
| const container = document.querySelector('.sector-images-container'); | |
| const scrollAmount = 200; | |
| if (direction === 'left') { | |
| container.scrollLeft -= scrollAmount; | |
| } else { | |
| container.scrollLeft += scrollAmount; | |
| } | |
| } catch (e) { | |
| console.error('Error scrolling container:', e); | |
| } | |
| } | |
| // Show sector description popup | |
| function showSectorDescription(description) { | |
| try { | |
| document.getElementById('sector-description').innerText = description || 'No description available.'; | |
| document.getElementById('sector-popup').style.display = 'flex'; | |
| document.body.classList.add('modal-open'); | |
| } catch (e) { | |
| console.error('Error showing sector description:', e); | |
| } | |
| } | |
| // Close sector popup | |
| function closePopup() { | |
| try { | |
| document.getElementById('sector-popup').style.display = 'none'; | |
| document.body.classList.remove('modal-open'); | |
| } catch (e) { | |
| console.error('Error closing popup:', e); | |
| } | |
| } | |
| // Toggle tier details with animation | |
| function toggleTierDetails() { | |
| try { | |
| const tierDetails = document.getElementById('tierDetails'); | |
| const arrowPath = document.getElementById('arrowPath'); | |
| const toggleButton = tierDetails.parentElement.querySelector('[aria-controls="tierDetails"]'); | |
| const isExpanded = !tierDetails.classList.contains('hidden'); | |
| tierDetails.classList.toggle('hidden'); | |
| arrowPath.setAttribute('d', tierDetails.classList.contains('hidden') ? 'M19 9l-7 7-7-7' : 'M19 15l-7-7-7 7'); | |
| toggleButton.setAttribute('aria-expanded', !isExpanded); | |
| } catch (e) { | |
| console.error('Error toggling tier details:', e); | |
| } | |
| } | |
| // Toggle ingredients section | |
| function toggleIngredients(index) { | |
| try { | |
| const section = document.getElementById(`ingredientsSection${index}`); | |
| const button = document.getElementById(`ingredientsToggleButton${index}`); | |
| if (!section || !button) { | |
| console.error(`Elements not found for index ${index}`); | |
| return; | |
| } | |
| const isHidden = section.classList.contains('hidden'); | |
| // Close all other ingredient sections | |
| document.querySelectorAll('.ingredients-section').forEach(s => { | |
| s.classList.add('hidden'); | |
| s.setAttribute('aria-hidden', 'true'); | |
| }); | |
| document.querySelectorAll('[id^="ingredientsToggleButton"]').forEach(b => { | |
| b.textContent = 'Show Ingredients'; | |
| b.setAttribute('aria-expanded', 'false'); | |
| }); | |
| // Toggle the current section | |
| if (isHidden) { | |
| section.classList.remove('hidden'); | |
| section.setAttribute('aria-hidden', 'false'); | |
| button.textContent = 'Hide Ingredients'; | |
| button.setAttribute('aria-expanded', 'true'); | |
| } else { | |
| section.classList.add('hidden'); | |
| section.setAttribute('aria-hidden', 'true'); | |
| button.textContent = 'Show Ingredients'; | |
| button.setAttribute('aria-expanded', 'false'); | |
| } | |
| } catch (e) { | |
| console.error('Error toggling ingredients:', e); | |
| } | |
| } | |
| // Show ingredient details modal | |
| function showIngredientDetails(button) { | |
| try { | |
| const data = JSON.parse(button.getAttribute('data-ingredient')); | |
| const { index, name, image, health_benefits, fun_facts } = data; | |
| const healthBenefitsList = (health_benefits || '').split(',').filter(item => item.trim()); | |
| const funFactsList = (fun_facts || '').split(',').filter(item => item.trim()); | |
| const modal = document.getElementById(`ingredientModal${index}`); | |
| if (!modal) { | |
| console.error(`Modal not found for index ${index}`); | |
| return; | |
| } | |
| document.getElementById(`modalTitle${index}`).textContent = name || 'Unknown Ingredient'; | |
| const modalImage = document.getElementById(`modalImage${index}`); | |
| modalImage.src = image || '/static/placeholder.jpg'; | |
| modalImage.alt = name || 'Ingredient image'; | |
| const healthBenefitsUl = document.getElementById(`healthBenefits${index}`); | |
| const funFactsUl = document.getElementById(`funFacts${index}`); | |
| healthBenefitsUl.innerHTML = healthBenefitsList.length | |
| ? healthBenefitsList.map(item => `<li>${item.trim()}</li>`).join('') | |
| : '<li>No health benefits available.</li>'; | |
| funFactsUl.innerHTML = funFactsList.length | |
| ? funFactsList.map(item => `<li>${item.trim()}</li>`).join('') | |
| : '<li>No fun facts available.</li>'; | |
| modal.style.display = 'flex'; | |
| document.body.classList.add('modal-open'); | |
| } catch (e) { | |
| console.error('Error showing ingredient details:', e); | |
| } | |
| } | |
| // Close ingredient modal | |
| function closeModal(index) { | |
| try { | |
| const modal = document.getElementById(`ingredientModal${index}`); | |
| if (modal) { | |
| modal.style.display = 'none'; | |
| document.body.classList.remove('modal-open'); | |
| } | |
| } catch (e) { | |
| console.error('Error closing modal:', e); | |
| } | |
| } | |
| // Delivery timer function | |
| function startDeliveryTimer(minutes) { | |
| try { | |
| const timerElement = document.getElementById('deliveryTimer'); | |
| const successMessage = document.getElementById('successMessage'); | |
| const deliveryText = document.querySelector('.order-confirmed p.text-center.text-gray-600'); | |
| let totalSeconds = minutes * 60; | |
| const updateTimer = () => { | |
| if (totalSeconds <= 0) { | |
| clearInterval(timerInterval); | |
| deliveryText.classList.add('hidden'); | |
| successMessage.classList.remove('hidden'); | |
| return; | |
| } | |
| const mins = Math.floor(totalSeconds / 60); | |
| const secs = totalSeconds % 60; | |
| timerElement.textContent = `${mins}:${secs < 10 ? '0' : ''}${secs}`; | |
| totalSeconds--; | |
| }; | |
| updateTimer(); // Initial call | |
| const timerInterval = setInterval(updateTimer, 1000); | |
| } catch (e) { | |
| console.error('Error in delivery timer:', e); | |
| } | |
| } | |
| // Set progress bar color based on points and start delivery timer | |
| document.addEventListener('DOMContentLoaded', () => { | |
| try { | |
| // Progress bar color | |
| const progressBar = document.getElementById('progressBar'); | |
| const pointsDisplay = document.getElementById('pointsDisplay'); | |
| const points = parseInt(pointsDisplay.textContent, 10) || 0; | |
| if (points <= 100) { | |
| progressBar.classList.add('range-0-100'); | |
| } else if (points <= 200) { | |
| progressBar.classList.add('range-100-200'); | |
| } else { | |
| progressBar.classList.add('range-200-plus'); | |
| } | |
| // Start delivery timer | |
| const deliveryTimeElement = document.getElementById('deliveryTimer'); | |
| const deliveryTime = parseInt(deliveryTimeElement.textContent.split(':')[0], 10) || 0; | |
| if (deliveryTime > 0) { | |
| startDeliveryTimer(deliveryTime); | |
| } | |
| // Lazy load images with IntersectionObserver | |
| const images = document.querySelectorAll('img.lazyload'); | |
| if ('IntersectionObserver' in window) { | |
| const observer = new IntersectionObserver((entries, observer) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const img = entry.target; | |
| img.src = img.src || '/static/placeholder.jpg'; | |
| img.classList.remove('lazyload'); | |
| observer.unobserve(img); | |
| } | |
| }); | |
| }); | |
| images.forEach(img => observer.observe(img)); | |
| } else { | |
| images.forEach(img => { | |
| img.src = img.src || '/static/placeholder.jpg'; | |
| img.classList.remove('lazyload'); | |
| }); | |
| } | |
| } catch (e) { | |
| console.error('Error in DOMContentLoaded:', e); | |
| } | |
| }); | |
| // Close modals on outside click | |
| document.addEventListener('click', (e) => { | |
| try { | |
| if (e.target.id === 'sector-popup') { | |
| closePopup(); | |
| } | |
| document.querySelectorAll('.ingredient-modal-container').forEach(modal => { | |
| if (e.target === modal) { | |
| const index = modal.id.replace('ingredientModal', ''); | |
| closeModal(index); | |
| } | |
| }); | |
| } catch (e) { | |
| console.error('Error handling outside click:', e); | |
| } | |
| }); | |
| // Keyboard navigation for scroll buttons | |
| document.querySelectorAll('.scroll-button').forEach(button => { | |
| button.addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| scrollContainer(button.classList.contains('left') ? 'left' : 'right'); | |
| } | |
| }); | |
| }); | |
| // Keyboard navigation for toggle buttons | |
| document.querySelectorAll('[id^="ingredientsToggleButton"]').forEach(button => { | |
| button.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| const index = button.getAttribute('data-index'); | |
| toggleIngredients(index); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |