Kgshop commited on
Commit
5e5ef58
·
verified ·
1 Parent(s): ba792c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -116
app.py CHANGED
@@ -363,7 +363,6 @@ CATALOG_TEMPLATE = '''
363
  <link rel="preconnect" href="https://fonts.googleapis.com">
364
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
365
  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;700&display=swap" rel="stylesheet">
366
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.css">
367
  <style>
368
  :root {
369
  --bg-color: #0d0d0d;
@@ -407,7 +406,7 @@ CATALOG_TEMPLATE = '''
407
  #cart-button span { position: absolute; top: -5px; right: -5px; background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--text-color); border-radius: 50%; padding: 3px 8px; font-size: 0.8rem; font-weight: bold; }
408
  .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); overflow-y: auto; }
409
  .modal-content { background: var(--surface-color); margin: 3% auto; padding: 0; border-radius: 8px; width: 95%; max-width: 900px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); animation: fadeIn 0.4s ease-out; position: relative; border: 1px solid var(--border-color); }
410
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
411
  .close { position: absolute; top: 15px; right: 20px; font-size: 2rem; color: var(--text-muted); cursor: pointer; transition: color 0.3s; line-height: 1; z-index: 10; }
412
  .close:hover { color: var(--text-color); }
413
  .modal-header { padding: 30px; border-bottom: 1px solid var(--border-color); }
@@ -448,6 +447,10 @@ CATALOG_TEMPLATE = '''
448
  .nav-button:hover, .nav-button.active { color: var(--primary-accent); }
449
  .nav-button i { font-size: 1.5rem; }
450
  .address-list p { margin-bottom: 10px; }
 
 
 
 
451
  </style>
452
  </head>
453
  <body>
@@ -459,20 +462,16 @@ CATALOG_TEMPLATE = '''
459
  <a href="{{ url_for('set_language', language='kk') }}" class="{{ 'active' if lang == 'kk' }}">KZ</a>
460
  </div>
461
  </div>
462
-
463
  <div class="store-address">{{ _('our_address') }} {{ store_address }}</div>
464
-
465
  <div class="filters-container">
466
  <button class="category-filter active" data-category="all">{{ _('all_categories') }}</button>
467
  {% for category in categories %}
468
  <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
469
  {% endfor %}
470
  </div>
471
-
472
  <div class="search-container">
473
  <input type="text" id="search-input" placeholder="{{ _('search_placeholder') }}">
474
  </div>
475
-
476
  <div class="products-grid" id="products-grid">
477
  {% for product in products %}
478
  <div class="product"
@@ -516,14 +515,12 @@ CATALOG_TEMPLATE = '''
516
  {% endif %}
517
  </div>
518
  </div>
519
-
520
  <div id="productModal" class="modal">
521
  <div class="modal-content">
522
  <span class="close" onclick="closeModal('productModal')" aria-label="{{ _('close') }}">×</span>
523
  <div id="modalContent">{{ _('loading') }}...</div>
524
  </div>
525
  </div>
526
-
527
  <div id="cartModal" class="modal">
528
  <div class="modal-content">
529
  <div class="modal-header">
@@ -549,7 +546,6 @@ CATALOG_TEMPLATE = '''
549
  </div>
550
  </div>
551
  </div>
552
-
553
  <div id="favoritesModal" class="modal">
554
  <div class="modal-content">
555
  <div class="modal-header">
@@ -558,7 +554,6 @@ CATALOG_TEMPLATE = '''
558
  <div class="modal-body" id="favoritesContent"><p style="text-align: center; padding: 20px;">{{ _('favorites_is_empty') }}</p></div>
559
  </div>
560
  </div>
561
-
562
  <div id="addressModal" class="modal">
563
  <div class="modal-content">
564
  <div class="modal-header">
@@ -571,12 +566,10 @@ CATALOG_TEMPLATE = '''
571
  </div>
572
  </div>
573
  </div>
574
-
575
  <button id="cart-button" onclick="openCartModal()" aria-label="{{ _('open_cart') }}">
576
  <i class="fas fa-shopping-cart"></i>
577
  <span id="cart-count">0</span>
578
  </button>
579
-
580
  <div class="bottom-nav">
581
  <a href="https://api.whatsapp.com/send?phone=+77477174564" target="_blank" class="nav-button">
582
  <i class="fab fa-whatsapp"></i>
@@ -591,10 +584,11 @@ CATALOG_TEMPLATE = '''
591
  <span>{{ _('favorites') }}</span>
592
  </button>
593
  </div>
594
-
595
  <div id="notification-placeholder"></div>
596
-
597
- <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.js"></script>
 
 
598
  <script>
599
  const products = {{ products|tojson }};
600
  const employees = {{ employees|tojson }};
@@ -603,7 +597,7 @@ CATALOG_TEMPLATE = '''
603
  const t = {{ translations[lang]|tojson|safe }};
604
  let cart = JSON.parse(localStorage.getItem('zhanPostelCart') || '[]');
605
  let favorites = JSON.parse(localStorage.getItem('zhanPostelFavorites') || '[]');
606
-
607
  function openModal(modalId) {
608
  const modal = document.getElementById(modalId);
609
  if (modal) {
@@ -611,7 +605,7 @@ CATALOG_TEMPLATE = '''
611
  document.body.style.overflow = 'hidden';
612
  }
613
  }
614
-
615
  function openModalByIndex(index) {
616
  loadProductDetails(index);
617
  openModal('productModal');
@@ -627,7 +621,7 @@ CATALOG_TEMPLATE = '''
627
  document.body.style.overflow = 'auto';
628
  }
629
  }
630
-
631
  function loadProductDetails(index) {
632
  const modalContent = document.getElementById('modalContent');
633
  if (!modalContent) return;
@@ -639,7 +633,6 @@ CATALOG_TEMPLATE = '''
639
  })
640
  .then(data => {
641
  modalContent.innerHTML = data;
642
- initializeSwiper();
643
  })
644
  .catch(error => {
645
  console.error('Error loading product details:', error);
@@ -647,60 +640,71 @@ CATALOG_TEMPLATE = '''
647
  });
648
  }
649
 
650
- function initializeSwiper() {
651
- const swiperContainer = document.querySelector('#productModal .swiper-container');
652
- if (swiperContainer) {
653
- new Swiper(swiperContainer, {
654
- loop: true,
655
- pagination: { el: '.swiper-pagination', clickable: true },
656
- navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
657
- });
658
  }
 
 
659
  }
660
 
661
- function addToCartFromDetail(buttonElement, productId, photoFilename) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  const product = products.find(p => p.id === productId);
663
  if (!product) {
664
  alert(t.product_add_error);
665
  return;
666
  }
667
- const controlsContainer = buttonElement.closest('.slide-controls');
668
  const quantityInput = controlsContainer.querySelector('.quantity-input');
669
  const sizeSelect = controlsContainer.querySelector('.size-select');
670
-
671
  const quantity = parseInt(quantityInput.value);
672
  const size = (sizeSelect && sizeSelect.value) ? sizeSelect.value : 'N/A';
673
-
674
  if (isNaN(quantity) || quantity <= 0) {
675
  alert(t.enter_correct_quantity);
676
  quantityInput.focus();
677
  return;
678
  }
679
-
680
- const cartItemId = `${product.id}-${photoFilename}-${size}`;
681
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
682
-
683
  if (existingItemIndex > -1) {
684
  cart[existingItemIndex].quantity += quantity;
685
  } else {
686
  cart.push({
687
  id: cartItemId, productId: product.id, name: product.name, price: product.price,
688
- photo: photoFilename, quantity: quantity, size: size
689
  });
690
  }
691
-
692
  localStorage.setItem('zhanPostelCart', JSON.stringify(cart));
693
  updateCartButton();
694
  showNotification(`${product.name} ${t.product_added_notification}`);
695
  }
696
 
697
-
698
  function updateCartButton() {
699
  const cartCountElement = document.getElementById('cart-count');
700
  const cartButton = document.getElementById('cart-button');
701
  if (!cartCountElement || !cartButton) return;
702
- const totalItems = cart.length;
703
-
704
  if (totalItems > 0) {
705
  cartCountElement.textContent = totalItems;
706
  cartButton.style.display = 'flex';
@@ -709,13 +713,12 @@ CATALOG_TEMPLATE = '''
709
  cartButton.style.display = 'none';
710
  }
711
  }
712
-
713
  function openCartModal() {
714
  const cartContent = document.getElementById('cartContent');
715
  const cartTotalElement = document.getElementById('cartTotal');
716
  if (!cartContent || !cartTotalElement) return;
717
  let total = 0;
718
-
719
  if (cart.length === 0) {
720
  cartContent.innerHTML = `<p style="text-align: center; padding: 20px;">${t.cart_is_empty}</p>`;
721
  cartTotalElement.textContent = '0.00';
@@ -727,7 +730,6 @@ CATALOG_TEMPLATE = '''
727
  ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}`
728
  : 'https://via.placeholder.com/80x80.png?text=N/A';
729
  const sizeText = item.size !== 'N/A' ? ` (${item.size})` : '';
730
-
731
  return `
732
  <div class="cart-item">
733
  <img src="${photoUrl}" alt="${item.name}">
@@ -741,7 +743,6 @@ CATALOG_TEMPLATE = '''
741
  }).join('');
742
  cartTotalElement.textContent = total.toFixed(0);
743
  }
744
-
745
  const employeeSelect = document.getElementById('employeeSelect');
746
  if (employeeSelect) {
747
  employeeSelect.innerHTML = `<option value="${t.online_order}">${t.online_order}</option>`;
@@ -752,17 +753,16 @@ CATALOG_TEMPLATE = '''
752
  employeeSelect.appendChild(option);
753
  });
754
  }
755
-
756
  openModal('cartModal');
757
  }
758
-
759
  function removeFromCart(itemId) {
760
  cart = cart.filter(item => item.id !== itemId);
761
  localStorage.setItem('zhanPostelCart', JSON.stringify(cart));
762
  openCartModal();
763
  updateCartButton();
764
  }
765
-
766
  function clearCart() {
767
  if (confirm(t.confirm_clear_cart)) {
768
  cart = [];
@@ -771,7 +771,7 @@ CATALOG_TEMPLATE = '''
771
  updateCartButton();
772
  }
773
  }
774
-
775
  function formulateOrder() {
776
  if (cart.length === 0) {
777
  alert(t.cart_empty_error);
@@ -810,24 +810,21 @@ CATALOG_TEMPLATE = '''
810
  if (formulateButton) formulateButton.disabled = false;
811
  });
812
  }
813
-
814
  function filterProducts() {
815
  const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
816
  const activeCategoryButton = document.querySelector('.category-filter.active');
817
  const activeCategory = activeCategoryButton ? activeCategoryButton.dataset.category : 'all';
818
  const grid = document.getElementById('products-grid');
819
  let visibleProducts = 0;
820
-
821
  const existingNoResults = grid.querySelector('.no-results-message');
822
  if (existingNoResults) existingNoResults.remove();
823
-
824
  document.querySelectorAll('.products-grid .product').forEach(productElement => {
825
  const name = productElement.getAttribute('data-name');
826
  const description = productElement.getAttribute('data-description');
827
  const category = productElement.getAttribute('data-category');
828
  const matchesSearch = !searchTerm || name.includes(searchTerm) || description.includes(searchTerm);
829
  const matchesCategory = activeCategory === 'all' || category === activeCategory;
830
-
831
  if (matchesSearch && matchesCategory) {
832
  productElement.style.display = 'flex';
833
  visibleProducts++;
@@ -835,7 +832,6 @@ CATALOG_TEMPLATE = '''
835
  productElement.style.display = 'none';
836
  }
837
  });
838
-
839
  if (visibleProducts === 0 && products.length > 0) {
840
  const p = document.createElement('p');
841
  p.className = 'no-results-message';
@@ -848,7 +844,7 @@ CATALOG_TEMPLATE = '''
848
  grid.appendChild(p);
849
  }
850
  }
851
-
852
  function setupFilters() {
853
  const searchInput = document.getElementById('search-input');
854
  const categoryFilters = document.querySelectorAll('.category-filter');
@@ -896,11 +892,10 @@ CATALOG_TEMPLATE = '''
896
  }, 250);
897
  }
898
  }
899
-
900
  function openFavoritesModal() {
901
  const favoritesContent = document.getElementById('favoritesContent');
902
  favoritesContent.innerHTML = '';
903
-
904
  if (favorites.length === 0) {
905
  favoritesContent.innerHTML = `<p style="text-align: center; padding: 20px;">${t.favorites_is_empty}</p>`;
906
  } else {
@@ -909,7 +904,6 @@ CATALOG_TEMPLATE = '''
909
  const photoUrl = item.photos && item.photos.length > 0
910
  ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photos[0]}`
911
  : 'https://via.placeholder.com/80x80.png?text=N/A';
912
-
913
  const itemHtml = `
914
  <div class="cart-item" onclick="openFavoriteProductDetail('${item.id}')" style="cursor: pointer;">
915
  <img src="${photoUrl}" alt="${item.name}">
@@ -923,7 +917,6 @@ CATALOG_TEMPLATE = '''
923
  favoritesContent.innerHTML += itemHtml;
924
  });
925
  }
926
-
927
  openModal('favoritesModal');
928
  }
929
 
@@ -937,7 +930,7 @@ CATALOG_TEMPLATE = '''
937
  updateFavoriteIcons();
938
  }
939
  }
940
-
941
  function showNotification(message, duration = 3000) {
942
  const placeholder = document.getElementById('notification-placeholder');
943
  if (!placeholder) return;
@@ -952,7 +945,7 @@ CATALOG_TEMPLATE = '''
952
  notification.addEventListener('transitionend', () => notification.remove());
953
  }, duration);
954
  }
955
-
956
  document.addEventListener('DOMContentLoaded', () => {
957
  updateCartButton();
958
  setupFilters();
@@ -967,6 +960,10 @@ CATALOG_TEMPLATE = '''
967
  document.querySelectorAll('.modal[style*="display: block"]').forEach(modal => {
968
  closeModal(modal.id);
969
  });
 
 
 
 
970
  }
971
  });
972
  });
@@ -977,69 +974,85 @@ CATALOG_TEMPLATE = '''
977
 
978
  PRODUCT_DETAIL_TEMPLATE = '''
979
  <style>
980
- .product-detail-layout { display: grid; grid-template-columns: 1fr; }
981
- @media (min-width: 900px) { .product-detail-layout { grid-template-columns: 3fr 2fr; gap: 40px; } }
982
- .product-detail-info { padding: 30px; }
983
- .product-detail-info h2 { font-size: 2rem; font-weight: 500; margin-bottom: 20px; }
984
- .product-detail-info .price { font-size: 1.8rem; font-weight: 500; margin-bottom: 25px; }
985
- .product-detail-info p { margin-bottom: 15px; color: var(--text-muted); }
986
- .product-detail-info strong { color: var(--text-color); }
987
- .swiper-container-detail { width: 100%; height: 100%; background-color: var(--bg-color); }
988
- .swiper-slide-detail { display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; }
989
- .swiper-slide-detail img { display: block; width: 100%; max-height: 70vh; object-fit: contain; }
990
- .swiper-pagination-bullet-active { background: var(--primary-accent) !important; }
991
- .swiper-button-next, .swiper-button-prev { color: var(--primary-accent) !important; }
992
- .slide-controls { padding: 20px; background-color: rgba(0,0,0,0.5); width: 100%; }
993
- .slide-controls label { display: block; text-align: left; margin-bottom: 5px; font-size: 0.9rem; color: var(--text-muted); }
994
- .slide-controls .form-control { width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; font-size: 1rem; background-color: var(--bg-color); color: var(--text-color); margin-bottom: 15px; }
995
- .slide-controls .product-button { width: 100%; }
 
 
 
 
 
 
 
 
996
  </style>
997
  <div class="product-detail-layout">
998
- <div class="swiper-container-detail">
999
- <div class="swiper-wrapper">
1000
- {% if product.get('photos') and product['photos']|length > 0 %}
1001
- {% for photo in product['photos'] %}
1002
- <div class="swiper-slide swiper-slide-detail">
1003
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product['name'] }} - photo {{ loop.index }}">
1004
- <div class="slide-controls">
1005
- {% set sizes = product.get('sizes', [])|select('ne', '')|list %}
1006
- {% if sizes %}
1007
- <label>{{ _('size_variant') }}</label>
1008
- <select class="form-control size-select">
1009
- {% for size in sizes %}
1010
- <option value="{{ size }}">{{ size }}</option>
1011
- {% endfor %}
1012
- </select>
1013
- {% endif %}
1014
- <label>{{ _('quantity') }}</label>
1015
- <input type="number" class="form-control quantity-input" value="1" min="1">
1016
- <button class="product-button" onclick="addToCartFromDetail(this, '{{ product.id }}', '{{ photo }}')">{{ _('add_to_cart') }}</button>
1017
- </div>
1018
- </div>
1019
- {% endfor %}
1020
- {% else %}
1021
- <div class="swiper-slide swiper-slide-detail">
1022
- <img src="https://via.placeholder.com/600x600.png?text=No+Image" alt="No image available">
1023
- <div class="slide-controls"><p>{{ _('no_description') }}</p></div>
1024
- </div>
1025
- {% endif %}
1026
  </div>
1027
- {% if product.get('photos') and product['photos']|length > 1 %}
1028
- <div class="swiper-pagination"></div>
1029
- <div class="swiper-button-next"></div>
1030
- <div class="swiper-button-prev"></div>
1031
  {% endif %}
1032
  </div>
1033
-
1034
  <div class="product-detail-info">
1035
- <h2>{{ product['name'] }}</h2>
1036
- <p class="price">{{ "%.0f"|format(product['price']) }} {{ currency_code }}</p>
1037
- <p><strong>{{ _('category') }}</strong> {{ product.get('category', _('no_category')) }}</p>
1038
- <p><strong>{{ _('description') }}</strong><br> {{ product.get('description', _('no_description'))|replace('\\n', '<br>')|safe }}</p>
1039
- {% set sizes = product.get('sizes', [])|select('ne', '')|list %}
1040
- {% if sizes %}
1041
- <p><strong>{{ _('available_sizes') }}</strong> {{ sizes|join(', ') }}</p>
1042
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
  </div>
1044
  </div>
1045
  '''
 
363
  <link rel="preconnect" href="https://fonts.googleapis.com">
364
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
365
  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;700&display=swap" rel="stylesheet">
 
366
  <style>
367
  :root {
368
  --bg-color: #0d0d0d;
 
406
  #cart-button span { position: absolute; top: -5px; right: -5px; background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--text-color); border-radius: 50%; padding: 3px 8px; font-size: 0.8rem; font-weight: bold; }
407
  .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); overflow-y: auto; }
408
  .modal-content { background: var(--surface-color); margin: 3% auto; padding: 0; border-radius: 8px; width: 95%; max-width: 900px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); animation: fadeIn 0.4s ease-out; position: relative; border: 1px solid var(--border-color); }
409
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
410
  .close { position: absolute; top: 15px; right: 20px; font-size: 2rem; color: var(--text-muted); cursor: pointer; transition: color 0.3s; line-height: 1; z-index: 10; }
411
  .close:hover { color: var(--text-color); }
412
  .modal-header { padding: 30px; border-bottom: 1px solid var(--border-color); }
 
447
  .nav-button:hover, .nav-button.active { color: var(--primary-accent); }
448
  .nav-button i { font-size: 1.5rem; }
449
  .address-list p { margin-bottom: 10px; }
450
+ .lightbox { display: none; position: fixed; z-index: 1003; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.9); justify-content: center; align-items: center; animation: fadeIn 0.3s; }
451
+ .lightbox-content { max-width: 90%; max-height: 90%; object-fit: contain; }
452
+ .lightbox-close { position: absolute; top: 20px; right: 35px; color: #fff; font-size: 40px; font-weight: bold; cursor: pointer; transition: color 0.3s; }
453
+ .lightbox-close:hover { color: #ccc; }
454
  </style>
455
  </head>
456
  <body>
 
462
  <a href="{{ url_for('set_language', language='kk') }}" class="{{ 'active' if lang == 'kk' }}">KZ</a>
463
  </div>
464
  </div>
 
465
  <div class="store-address">{{ _('our_address') }} {{ store_address }}</div>
 
466
  <div class="filters-container">
467
  <button class="category-filter active" data-category="all">{{ _('all_categories') }}</button>
468
  {% for category in categories %}
469
  <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
470
  {% endfor %}
471
  </div>
 
472
  <div class="search-container">
473
  <input type="text" id="search-input" placeholder="{{ _('search_placeholder') }}">
474
  </div>
 
475
  <div class="products-grid" id="products-grid">
476
  {% for product in products %}
477
  <div class="product"
 
515
  {% endif %}
516
  </div>
517
  </div>
 
518
  <div id="productModal" class="modal">
519
  <div class="modal-content">
520
  <span class="close" onclick="closeModal('productModal')" aria-label="{{ _('close') }}">×</span>
521
  <div id="modalContent">{{ _('loading') }}...</div>
522
  </div>
523
  </div>
 
524
  <div id="cartModal" class="modal">
525
  <div class="modal-content">
526
  <div class="modal-header">
 
546
  </div>
547
  </div>
548
  </div>
 
549
  <div id="favoritesModal" class="modal">
550
  <div class="modal-content">
551
  <div class="modal-header">
 
554
  <div class="modal-body" id="favoritesContent"><p style="text-align: center; padding: 20px;">{{ _('favorites_is_empty') }}</p></div>
555
  </div>
556
  </div>
 
557
  <div id="addressModal" class="modal">
558
  <div class="modal-content">
559
  <div class="modal-header">
 
566
  </div>
567
  </div>
568
  </div>
 
569
  <button id="cart-button" onclick="openCartModal()" aria-label="{{ _('open_cart') }}">
570
  <i class="fas fa-shopping-cart"></i>
571
  <span id="cart-count">0</span>
572
  </button>
 
573
  <div class="bottom-nav">
574
  <a href="https://api.whatsapp.com/send?phone=+77477174564" target="_blank" class="nav-button">
575
  <i class="fab fa-whatsapp"></i>
 
584
  <span>{{ _('favorites') }}</span>
585
  </button>
586
  </div>
 
587
  <div id="notification-placeholder"></div>
588
+ <div id="lightbox" class="lightbox" onclick="closeLightbox(event)">
589
+ <span class="lightbox-close" onclick="closeLightbox()">&times;</span>
590
+ <img class="lightbox-content" id="lightbox-img">
591
+ </div>
592
  <script>
593
  const products = {{ products|tojson }};
594
  const employees = {{ employees|tojson }};
 
597
  const t = {{ translations[lang]|tojson|safe }};
598
  let cart = JSON.parse(localStorage.getItem('zhanPostelCart') || '[]');
599
  let favorites = JSON.parse(localStorage.getItem('zhanPostelFavorites') || '[]');
600
+
601
  function openModal(modalId) {
602
  const modal = document.getElementById(modalId);
603
  if (modal) {
 
605
  document.body.style.overflow = 'hidden';
606
  }
607
  }
608
+
609
  function openModalByIndex(index) {
610
  loadProductDetails(index);
611
  openModal('productModal');
 
621
  document.body.style.overflow = 'auto';
622
  }
623
  }
624
+
625
  function loadProductDetails(index) {
626
  const modalContent = document.getElementById('modalContent');
627
  if (!modalContent) return;
 
633
  })
634
  .then(data => {
635
  modalContent.innerHTML = data;
 
636
  })
637
  .catch(error => {
638
  console.error('Error loading product details:', error);
 
640
  });
641
  }
642
 
643
+ function updateMainPhoto(newSrc, thumbElement) {
644
+ const mainPhoto = document.getElementById('main-product-photo');
645
+ if (mainPhoto) {
646
+ mainPhoto.style.opacity = 0;
647
+ setTimeout(() => {
648
+ mainPhoto.src = newSrc;
649
+ mainPhoto.style.opacity = 1;
650
+ }, 200);
651
  }
652
+ document.querySelectorAll('#thumbnail-grid .thumbnail').forEach(t => t.classList.remove('active'));
653
+ thumbElement.classList.add('active');
654
  }
655
 
656
+ function openLightbox(src) {
657
+ const lightbox = document.getElementById('lightbox');
658
+ if (lightbox) {
659
+ document.getElementById('lightbox-img').src = src;
660
+ lightbox.style.display = 'flex';
661
+ }
662
+ }
663
+
664
+ function closeLightbox(event) {
665
+ const lightbox = document.getElementById('lightbox');
666
+ if (lightbox && (!event || event.target === lightbox || event.target.classList.contains('lightbox-close'))) {
667
+ lightbox.style.display = 'none';
668
+ }
669
+ }
670
+
671
+ function addToCartFromDetail(productId) {
672
  const product = products.find(p => p.id === productId);
673
  if (!product) {
674
  alert(t.product_add_error);
675
  return;
676
  }
677
+ const controlsContainer = document.getElementById('modalContent');
678
  const quantityInput = controlsContainer.querySelector('.quantity-input');
679
  const sizeSelect = controlsContainer.querySelector('.size-select');
 
680
  const quantity = parseInt(quantityInput.value);
681
  const size = (sizeSelect && sizeSelect.value) ? sizeSelect.value : 'N/A';
 
682
  if (isNaN(quantity) || quantity <= 0) {
683
  alert(t.enter_correct_quantity);
684
  quantityInput.focus();
685
  return;
686
  }
687
+ const cartItemId = `${product.id}-${size}`;
 
688
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
689
+ const photoForCart = (product.photos && product.photos.length > 0) ? product.photos[0] : null;
690
  if (existingItemIndex > -1) {
691
  cart[existingItemIndex].quantity += quantity;
692
  } else {
693
  cart.push({
694
  id: cartItemId, productId: product.id, name: product.name, price: product.price,
695
+ photo: photoForCart, quantity: quantity, size: size
696
  });
697
  }
 
698
  localStorage.setItem('zhanPostelCart', JSON.stringify(cart));
699
  updateCartButton();
700
  showNotification(`${product.name} ${t.product_added_notification}`);
701
  }
702
 
 
703
  function updateCartButton() {
704
  const cartCountElement = document.getElementById('cart-count');
705
  const cartButton = document.getElementById('cart-button');
706
  if (!cartCountElement || !cartButton) return;
707
+ const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
 
708
  if (totalItems > 0) {
709
  cartCountElement.textContent = totalItems;
710
  cartButton.style.display = 'flex';
 
713
  cartButton.style.display = 'none';
714
  }
715
  }
716
+
717
  function openCartModal() {
718
  const cartContent = document.getElementById('cartContent');
719
  const cartTotalElement = document.getElementById('cartTotal');
720
  if (!cartContent || !cartTotalElement) return;
721
  let total = 0;
 
722
  if (cart.length === 0) {
723
  cartContent.innerHTML = `<p style="text-align: center; padding: 20px;">${t.cart_is_empty}</p>`;
724
  cartTotalElement.textContent = '0.00';
 
730
  ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}`
731
  : 'https://via.placeholder.com/80x80.png?text=N/A';
732
  const sizeText = item.size !== 'N/A' ? ` (${item.size})` : '';
 
733
  return `
734
  <div class="cart-item">
735
  <img src="${photoUrl}" alt="${item.name}">
 
743
  }).join('');
744
  cartTotalElement.textContent = total.toFixed(0);
745
  }
 
746
  const employeeSelect = document.getElementById('employeeSelect');
747
  if (employeeSelect) {
748
  employeeSelect.innerHTML = `<option value="${t.online_order}">${t.online_order}</option>`;
 
753
  employeeSelect.appendChild(option);
754
  });
755
  }
 
756
  openModal('cartModal');
757
  }
758
+
759
  function removeFromCart(itemId) {
760
  cart = cart.filter(item => item.id !== itemId);
761
  localStorage.setItem('zhanPostelCart', JSON.stringify(cart));
762
  openCartModal();
763
  updateCartButton();
764
  }
765
+
766
  function clearCart() {
767
  if (confirm(t.confirm_clear_cart)) {
768
  cart = [];
 
771
  updateCartButton();
772
  }
773
  }
774
+
775
  function formulateOrder() {
776
  if (cart.length === 0) {
777
  alert(t.cart_empty_error);
 
810
  if (formulateButton) formulateButton.disabled = false;
811
  });
812
  }
813
+
814
  function filterProducts() {
815
  const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
816
  const activeCategoryButton = document.querySelector('.category-filter.active');
817
  const activeCategory = activeCategoryButton ? activeCategoryButton.dataset.category : 'all';
818
  const grid = document.getElementById('products-grid');
819
  let visibleProducts = 0;
 
820
  const existingNoResults = grid.querySelector('.no-results-message');
821
  if (existingNoResults) existingNoResults.remove();
 
822
  document.querySelectorAll('.products-grid .product').forEach(productElement => {
823
  const name = productElement.getAttribute('data-name');
824
  const description = productElement.getAttribute('data-description');
825
  const category = productElement.getAttribute('data-category');
826
  const matchesSearch = !searchTerm || name.includes(searchTerm) || description.includes(searchTerm);
827
  const matchesCategory = activeCategory === 'all' || category === activeCategory;
 
828
  if (matchesSearch && matchesCategory) {
829
  productElement.style.display = 'flex';
830
  visibleProducts++;
 
832
  productElement.style.display = 'none';
833
  }
834
  });
 
835
  if (visibleProducts === 0 && products.length > 0) {
836
  const p = document.createElement('p');
837
  p.className = 'no-results-message';
 
844
  grid.appendChild(p);
845
  }
846
  }
847
+
848
  function setupFilters() {
849
  const searchInput = document.getElementById('search-input');
850
  const categoryFilters = document.querySelectorAll('.category-filter');
 
892
  }, 250);
893
  }
894
  }
895
+
896
  function openFavoritesModal() {
897
  const favoritesContent = document.getElementById('favoritesContent');
898
  favoritesContent.innerHTML = '';
 
899
  if (favorites.length === 0) {
900
  favoritesContent.innerHTML = `<p style="text-align: center; padding: 20px;">${t.favorites_is_empty}</p>`;
901
  } else {
 
904
  const photoUrl = item.photos && item.photos.length > 0
905
  ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photos[0]}`
906
  : 'https://via.placeholder.com/80x80.png?text=N/A';
 
907
  const itemHtml = `
908
  <div class="cart-item" onclick="openFavoriteProductDetail('${item.id}')" style="cursor: pointer;">
909
  <img src="${photoUrl}" alt="${item.name}">
 
917
  favoritesContent.innerHTML += itemHtml;
918
  });
919
  }
 
920
  openModal('favoritesModal');
921
  }
922
 
 
930
  updateFavoriteIcons();
931
  }
932
  }
933
+
934
  function showNotification(message, duration = 3000) {
935
  const placeholder = document.getElementById('notification-placeholder');
936
  if (!placeholder) return;
 
945
  notification.addEventListener('transitionend', () => notification.remove());
946
  }, duration);
947
  }
948
+
949
  document.addEventListener('DOMContentLoaded', () => {
950
  updateCartButton();
951
  setupFilters();
 
960
  document.querySelectorAll('.modal[style*="display: block"]').forEach(modal => {
961
  closeModal(modal.id);
962
  });
963
+ const lightbox = document.getElementById('lightbox');
964
+ if (lightbox && lightbox.style.display !== 'none') {
965
+ closeLightbox();
966
+ }
967
  }
968
  });
969
  });
 
974
 
975
  PRODUCT_DETAIL_TEMPLATE = '''
976
  <style>
977
+ .product-detail-layout { display: grid; grid-template-columns: 1fr; gap: 20px; padding: 15px; }
978
+ @media (min-width: 768px) { .product-detail-layout { grid-template-columns: 1fr 1fr; gap: 40px; padding: 30px; } }
979
+ .photo-gallery { display: flex; flex-direction: column; gap: 10px; }
980
+ .main-photo-container { width: 100%; aspect-ratio: 1 / 1; background-color: var(--bg-color); border-radius: 8px; overflow: hidden; cursor: zoom-in; position: relative; }
981
+ .main-photo { width: 100%; height: 100%; object-fit: cover; transition: opacity 0.2s ease-in-out; }
982
+ .thumbnail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 10px; }
983
+ .thumbnail { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 4px; cursor: pointer; border: 2px solid transparent; transition: border-color 0.3s ease; }
984
+ .thumbnail.active, .thumbnail:hover { border-color: var(--primary-accent); }
985
+ .product-detail-info { display: flex; flex-direction: column; }
986
+ .product-detail-info h2 { font-size: 2rem; font-weight: 500; margin-bottom: 10px; line-height: 1.2; }
987
+ .product-detail-info .price { font-size: 1.8rem; font-weight: 500; margin-bottom: 20px; }
988
+ .product-detail-info .description-block { margin-top: 15px; margin-bottom: 20px; }
989
+ .product-detail-info p { margin-bottom: 10px; color: var(--text-muted); line-height: 1.7; }
990
+ .product-detail-info strong { color: var(--text-color); font-weight: 500; }
991
+ .controls-section { margin-top: auto; padding-top: 20px; border-top: 1px solid var(--border-color); }
992
+ .control-group { margin-bottom: 15px; }
993
+ .control-group label { display: block; font-size: 1rem; color: var(--text-muted); margin-bottom: 8px; font-weight: 400; }
994
+ .control-group .form-control { width: 100%; padding: 14px; border: 1px solid var(--border-color); border-radius: 4px; font-size: 1rem; background-color: var(--bg-color); color: var(--text-color); -moz-appearance: textfield; }
995
+ .control-group input[type=number]::-webkit-inner-spin-button,
996
+ .control-group input[type=number]::-webkit-outer-spin-button {
997
+ -webkit-appearance: none;
998
+ margin: 0;
999
+ }
1000
+ .detail-add-to-cart-btn { width: 100%; padding: 16px; font-size: 1.1rem; }
1001
  </style>
1002
  <div class="product-detail-layout">
1003
+ {% set photos = product.get('photos', []) %}
1004
+ <div class="photo-gallery">
1005
+ <div class="main-photo-container" onclick="openLightbox(this.querySelector('img').src)">
1006
+ {% if photos %}
1007
+ <img id="main-product-photo" class="main-photo" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photos[0] }}" alt="{{ product.name }}">
1008
+ {% else %}
1009
+ <img id="main-product-photo" class="main-photo" src="https://via.placeholder.com/600x600.png?text=No+Image" alt="No image available">
1010
+ {% endif %}
1011
+ </div>
1012
+ {% if photos|length > 1 %}
1013
+ <div class="thumbnail-grid" id="thumbnail-grid">
1014
+ {% for photo in photos %}
1015
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}"
1016
+ alt="Thumbnail {{ loop.index }}"
1017
+ class="thumbnail {{ 'active' if loop.first }}"
1018
+ onclick="updateMainPhoto('https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}', this)">
1019
+ {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
1020
  </div>
 
 
 
 
1021
  {% endif %}
1022
  </div>
 
1023
  <div class="product-detail-info">
1024
+ <div>
1025
+ <h2>{{ product['name'] }}</h2>
1026
+ <p class="price">{{ "%.0f"|format(product['price']) }} {{ currency_code }}</p>
1027
+ <p><strong>{{ _('category') }}:</strong> {{ product.get('category', _('no_category')) }}</p>
1028
+ {% set sizes = product.get('sizes', [])|select('ne', '')|list %}
1029
+ {% if sizes %}
1030
+ <p><strong>{{ _('available_sizes') }}:</strong> {{ sizes|join(', ') }}</p>
1031
+ {% endif %}
1032
+ <div class="description-block">
1033
+ <p><strong>{{ _('description') }}:</strong><br>
1034
+ {{ product.get('description', _('no_description'))|replace('\\n', '<br>')|safe }}</p>
1035
+ </div>
1036
+ </div>
1037
+ <div class="controls-section">
1038
+ {% if sizes %}
1039
+ <div class="control-group">
1040
+ <label for="size-select">{{ _('size_variant') }}</label>
1041
+ <select id="size-select" class="form-control size-select">
1042
+ {% for size in sizes %}
1043
+ <option value="{{ size }}">{{ size }}</option>
1044
+ {% endfor %}
1045
+ </select>
1046
+ </div>
1047
+ {% endif %}
1048
+ <div class="control-group">
1049
+ <label for="quantity-input">{{ _('quantity') }}</label>
1050
+ <input type="number" id="quantity-input" class="form-control quantity-input" value="1" min="1">
1051
+ </div>
1052
+ <button class="product-button detail-add-to-cart-btn" onclick="addToCartFromDetail('{{ product.id }}')">
1053
+ <i class="fas fa-cart-plus"></i> {{ _('add_to_cart') }}
1054
+ </button>
1055
+ </div>
1056
  </div>
1057
  </div>
1058
  '''