templates/home/single-product.html.twig line 1

Open in your IDE?
  1. {% extends 'base_home.html.twig' %}
  2. {% block title %}
  3.     {{ product.name }}
  4.     | MaketOu
  5. {% endblock %}
  6. {% block stylesheets %}
  7.     <style>
  8.         /* Styles pour l'affichage de la boutique */
  9.         .shop-info {
  10.             padding: 10px 0;
  11.             border-bottom: 1px solid #f0f0f0;
  12.             margin-bottom: 15px;
  13.         }
  14.         .shop-link {
  15.             color: #ffa200;
  16.             text-decoration: none;
  17.             font-weight: 500;
  18.         }
  19.         .shop-link:hover {
  20.             color: #e8910a;
  21.             text-decoration: underline;
  22.         }
  23.         /* Styles pour les statistiques du produit */
  24.         .product-stats {
  25.             background: #f8f9fa;
  26.             padding: 15px;
  27.             border-radius: 8px;
  28.             border: 1px solid #e9ecef;
  29.         }
  30.         .product-stats .stat-item {
  31.             text-align: center;
  32.         }
  33.         .product-stats .stat-number {
  34.             display: block;
  35.             font-size: 1.2rem;
  36.             font-weight: bold;
  37.             color: #ffa200;
  38.         }
  39.         .product-stats .stat-label {
  40.             color: #666;
  41.             font-size: 0.8rem;
  42.             text-transform: uppercase;
  43.             letter-spacing: 0.5px;
  44.         }
  45.         /* Message de confirmation */
  46.         #cart-message {
  47.             border: none;
  48.             background: #d4edda;
  49.             color: #155724;
  50.             border-radius: 6px;
  51.             padding: 12px 16px;
  52.             margin-top: 15px;
  53.         }
  54.         /* Amélioration des boutons d'actions */
  55.         .card_area {
  56.             margin-top: 20px;
  57.             gap: 12px;
  58.         }
  59.         .card_area .primary-btn {
  60.             background: #ffa200;
  61.             border: none;
  62.             padding: 0 30px;
  63.             height: 50px;
  64.             display: inline-flex;
  65.             align-items: center;
  66.             justify-content: center;
  67.             border-radius: 8px;
  68.             color: white;
  69.             font-weight: 600;
  70.             font-size: 16px;
  71.             transition: all 0.3s ease;
  72.             text-decoration: none;
  73.             gap: 8px;
  74.             flex: 1;
  75.             max-width: 250px;
  76.         }
  77.         .card_area .primary-btn i {
  78.             font-size: 20px;
  79.         }
  80.         .card_area .primary-btn:hover {
  81.             background: #e8910a;
  82.             transform: translateY(-2px);
  83.             box-shadow: 0 4px 15px rgba(255, 162, 0, 0.3);
  84.             color: white;
  85.         }
  86.         .card_area .icon_btn {
  87.             display: inline-flex;
  88.             align-items: center;
  89.             justify-content: center;
  90.             width: 50px;
  91.             height: 50px;
  92.             border-radius: 8px;
  93.             background: #fff;
  94.             color: #333;
  95.             border: 1px solid #ddd;
  96.             transition: all 0.3s ease;
  97.             text-decoration: none;
  98.         }
  99.         .card_area .icon_btn i {
  100.             font-size: 22px;
  101.         }
  102.         .card_area .icon_btn:hover {
  103.             background: #ffa200;
  104.             color: white;
  105.             border-color: #ffa200;
  106.             transform: translateY(-2px);
  107.             box-shadow: 0 4px 12px rgba(255, 162, 0, 0.2);
  108.         }
  109.         /* Styles pour la galerie moderne de produits */
  110.         .product-gallery-modern {
  111.             display: flex;
  112.             gap: 15px;
  113.             position: relative;
  114.             align-items: stretch;
  115.         }
  116.         .thumbnails-container {
  117.             position: relative;
  118.             display: flex;
  119.             flex-direction: column;
  120.             align-items: center;
  121.             width: 80px;
  122.             flex-shrink: 0;
  123.             height: auto;
  124.         }
  125.         /* Les boutons sont toujours visibles (meilleur pour le mobile et l'UX) */
  126.         .thumbnails-container .thumbnail-nav-btn {
  127.             transition: all 0.2s ease;
  128.         }
  129.         .product-thumbnails {
  130.             display: flex;
  131.             flex-direction: column;
  132.             gap: 10px;
  133.             width: 100%;
  134.             flex: 1;
  135.             overflow: hidden;
  136.             position: relative;
  137.         }
  138.         .thumbnails-wrapper {
  139.             display: flex;
  140.             flex-direction: column;
  141.             gap: 10px;
  142.             transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  143.             will-change: transform;
  144.         }
  145.         .thumbnail-nav-btn {
  146.             width: 30px;
  147.             height: 30px;
  148.             border-radius: 50%;
  149.             background: white;
  150.             border: 2px solid #e0e0e0;
  151.             display: flex !important;
  152.             align-items: center;
  153.             justify-content: center;
  154.             cursor: pointer;
  155.             transition: all 0.3s ease;
  156.             margin: 0;
  157.             padding: 0;
  158.             color: #666;
  159.             z-index: 10;
  160.             box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  161.             position: relative;
  162.             flex-shrink: 0;
  163.         }
  164.         .thumbnail-nav-btn:hover {
  165.             background: #ffa200;
  166.             border-color: #ffa200;
  167.             color: white;
  168.             transform: scale(1.1);
  169.             box-shadow: 0 4px 12px rgba(255, 162, 0, 0.3);
  170.         }
  171.         .thumbnail-nav-btn:active {
  172.             transform: scale(0.95);
  173.         }
  174.         .thumbnail-nav-btn.disabled {
  175.             opacity: 0.3;
  176.             cursor: not-allowed;
  177.             pointer-events: none;
  178.             filter: grayscale(1);
  179.         }
  180.         .thumbnail-nav-btn i {
  181.             font-size: 20px;
  182.             font-weight: bold;
  183.         }
  184.         .thumbnail-item {
  185.             width: 80px;
  186.             height: 80px;
  187.             min-width: 80px;
  188.             min-height: 80px;
  189.             max-width: 80px;
  190.             max-height: 80px;
  191.             border: 2px solid #e0e0e0;
  192.             border-radius: 8px;
  193.             overflow: hidden;
  194.             cursor: pointer;
  195.             transition: border-color 0.3s ease, border-width 0.3s ease, box-shadow 0.3s ease;
  196.             position: relative;
  197.             background: #fff;
  198.             border-bottom: 2px solid #e0e0e0;
  199.             flex-shrink: 0;
  200.         }
  201.         .thumbnail-item:hover {
  202.             border-color: #ffa200;
  203.             border-bottom-color: #ffa200;
  204.             border-width: 3px;
  205.             box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2);
  206.         }
  207.         .thumbnail-item.active {
  208.             border-color: #ffa200 !important;
  209.             border-bottom-color: #ffa200 !important;
  210.             border-width: 3px !important;
  211.             box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2) !important;
  212.         }
  213.         .thumbnail-item::after,
  214.         .thumbnail-item::before {
  215.             display: none !important;
  216.             content: none !important;
  217.         }
  218.         .thumbnail-item img {
  219.             width: 100%;
  220.             height: 100%;
  221.             object-fit: cover;
  222.             transition: transform 0.3s ease;
  223.         }
  224.         .thumbnail-item:hover img {
  225.             transform: scale(1.1);
  226.         }
  227.         .thumbnail-item.active img {
  228.             transform: scale(1.1);
  229.         }
  230.         .product-main-image-wrapper {
  231.             flex: 1;
  232.             position: relative;
  233.             background: #fff;
  234.             border-radius: 12px;
  235.             overflow: hidden;
  236.             border: 1px solid #e0e0e0;
  237.             cursor: crosshair;
  238.         }
  239.         .product-main-image {
  240.             position: relative;
  241.             width: 100%;
  242.             padding-top: 100%;
  243.             overflow: hidden;
  244.             background: #f8f9fa;
  245.         }
  246.         .product-main-image img {
  247.             position: absolute;
  248.             top: 0;
  249.             left: 0;
  250.             width: 100%;
  251.             height: 100%;
  252.             object-fit: contain;
  253.             transition: transform 0.15s ease-out;
  254.             transform-origin: center center;
  255.         }
  256.         /* Supprimer le scale basique, géré en JS */
  257.         /* .product-main-image:hover img {
  258.             transform: scale(1.05);
  259.         } */
  260.         .image-overlay-icons {
  261.             position: absolute;
  262.             top: 15px;
  263.             right: 15px;
  264.             display: flex;
  265.             flex-direction: column;
  266.             gap: 10px;
  267.             z-index: 20;
  268.             pointer-events: none;
  269.         }
  270.         .icon-btn {
  271.             pointer-events: all;
  272.             width: 45px;
  273.             height: 45px;
  274.             border-radius: 50%;
  275.             background: rgba(255, 255, 255, 0.95);
  276.             border: none;
  277.             display: flex;
  278.             align-items: center;
  279.             justify-content: center;
  280.             padding: 0;
  281.             cursor: pointer;
  282.             transition: all 0.3s ease;
  283.             box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  284.             color: #333;
  285.         }
  286.         .icon-btn:hover {
  287.             background: #ffa200;
  288.             color: white;
  289.             transform: scale(1.1);
  290.             box-shadow: 0 4px 12px rgba(255, 162, 0, 0.4);
  291.         }
  292.         .icon-btn i {
  293.             font-size: 18px;
  294.         }
  295.         .favorite-btn.active {
  296.             background: #ff6b6b;
  297.             color: white;
  298.         }
  299.         .image-counter {
  300.             position: absolute;
  301.             bottom: 15px;
  302.             right: 15px;
  303.             background: rgba(0, 0, 0, 0.7);
  304.             color: white;
  305.             padding: 6px 12px;
  306.             border-radius: 20px;
  307.             font-size: 13px;
  308.             font-weight: 600;
  309.         }
  310.         /* Modal de zoom d'image avec transitions modernes */
  311.         .image-zoom-modal {
  312.             display: none;
  313.             position: fixed;
  314.             z-index: 9999;
  315.             left: 0;
  316.             top: 0;
  317.             width: 100%;
  318.             height: 100%;
  319.             background: rgba(0, 0, 0, 0);
  320.             overflow: auto;
  321.             opacity: 0;
  322.             transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
  323.             backdrop-filter: blur(0px);
  324.         }
  325.         .image-zoom-modal.active {
  326.             display: flex;
  327.             align-items: center;
  328.             justify-content: center;
  329.             opacity: 1;
  330.             background: rgba(0, 0, 0, 0.95);
  331.             backdrop-filter: blur(10px);
  332.         }
  333.         .zoom-modal-content {
  334.             position: relative;
  335.             max-width: 90%;
  336.             max-height: 90%;
  337.             margin: auto;
  338.             opacity: 0;
  339.             transform: scale(0.8);
  340.             transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  341.         }
  342.         .image-zoom-modal.active .zoom-modal-content {
  343.             opacity: 1;
  344.             transform: scale(1);
  345.         }
  346.         .zoom-modal-content img {
  347.             width: 100%;
  348.             height: auto;
  349.             max-height: 90vh;
  350.             object-fit: contain;
  351.             border-radius: 8px;
  352.             box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  353.             opacity: 0;
  354.             transition: opacity 0.3s ease;
  355.         }
  356.         .zoom-modal-content img.loaded {
  357.             opacity: 1;
  358.         }
  359.         /* Loader moderne */
  360.         .zoom-loader {
  361.             position: absolute;
  362.             top: 50%;
  363.             left: 50%;
  364.             transform: translate(-50%, -50%);
  365.             z-index: 1;
  366.             display: flex;
  367.             flex-direction: column;
  368.             align-items: center;
  369.             justify-content: center;
  370.         }
  371.         .zoom-loader-spinner {
  372.             width: 60px;
  373.             height: 60px;
  374.             border: 4px solid rgba(255, 255, 255, 0.1);
  375.             border-top-color: #ffa200;
  376.             border-radius: 50%;
  377.             animation: spin 1s linear infinite;
  378.         }
  379.         @keyframes spin {
  380.             to {
  381.                 transform: rotate(360deg);
  382.             }
  383.         }
  384.         .zoom-loader-text {
  385.             color: white;
  386.             margin-top: 15px;
  387.             text-align: center;
  388.             font-size: 14px;
  389.             font-weight: 500;
  390.         }
  391.         .close-zoom {
  392.             position: absolute;
  393.             top: 30px;
  394.             right: 30px;
  395.             color: white;
  396.             font-size: 32px;
  397.             font-weight: 300;
  398.             cursor: pointer;
  399.             z-index: 10000;
  400.             width: 50px;
  401.             height: 50px;
  402.             display: flex;
  403.             align-items: center;
  404.             justify-content: center;
  405.             background: rgba(255, 255, 255, 0.1);
  406.             border: 2px solid rgba(255, 255, 255, 0.2);
  407.             border-radius: 50%;
  408.             transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  409.             opacity: 0;
  410.             transform: scale(0.8);
  411.         }
  412.         .image-zoom-modal.active .close-zoom {
  413.             opacity: 1;
  414.             transform: scale(1);
  415.             transition-delay: 0.2s;
  416.         }
  417.         .close-zoom:hover {
  418.             background: rgba(255, 162, 0, 0.9);
  419.             border-color: #ffa200;
  420.             transform: scale(1.1) rotate(90deg);
  421.             box-shadow: 0 4px 20px rgba(255, 162, 0, 0.4);
  422.         }
  423.         /* Boutons de navigation sur l'image principale */
  424.         .main-image-nav {
  425.             position: absolute;
  426.             top: 0;
  427.             bottom: 0;
  428.             left: 0;
  429.             right: 0;
  430.             display: flex;
  431.             align-items: center;
  432.             justify-content: space-between;
  433.             padding: 0 15px;
  434.             pointer-events: none;
  435.             z-index: 15;
  436.         }
  437.         .main-image-nav-btn {
  438.             pointer-events: all;
  439.             width: 44px;
  440.             height: 44px;
  441.             border-radius: 50%;
  442.             background: rgba(255, 255, 255, 0.95);
  443.             border: 2px solid #e0e0e0;
  444.             display: flex;
  445.             align-items: center;
  446.             justify-content: center;
  447.             padding: 0;
  448.             box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
  449.             color: #333;
  450.             transition: all 0.2s ease;
  451.         }
  452.         .main-image-nav-btn:hover {
  453.             background: #ffa200;
  454.             border-color: #ffa200;
  455.             color: #fff;
  456.             transform: scale(1.08);
  457.         }
  458.         .main-image-nav-btn:active {
  459.             transform: scale(0.95);
  460.         }
  461.         /* Modal personnalisé pour remplacer les alert */
  462.         .custom-alert-modal .modal-content {
  463.             border-radius: 12px;
  464.             border: none;
  465.             box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  466.         }
  467.         .custom-alert-modal .modal-header {
  468.             border-bottom: none;
  469.             padding: 1.5rem 1.5rem 0.5rem;
  470.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  471.             color: white;
  472.             border-radius: 12px 12px 0 0;
  473.         }
  474.         .custom-alert-modal .modal-header .btn-close {
  475.             filter: brightness(0) invert(1);
  476.             opacity: 0.8;
  477.         }
  478.         .custom-alert-modal .modal-header .btn-close:hover {
  479.             opacity: 1;
  480.         }
  481.         .custom-alert-modal .modal-body {
  482.             padding: 1.5rem;
  483.             text-align: center;
  484.         }
  485.         .custom-alert-modal .modal-body .alert-icon {
  486.             font-size: 3rem;
  487.             margin-bottom: 1rem;
  488.         }
  489.         .custom-alert-modal .modal-body .alert-icon.success {
  490.             color: #28a745;
  491.         }
  492.         .custom-alert-modal .modal-body .alert-icon.error {
  493.             color: #dc3545;
  494.         }
  495.         .custom-alert-modal .modal-body .alert-icon.warning {
  496.             color: #ffc107;
  497.         }
  498.         .custom-alert-modal .modal-body .alert-icon.info {
  499.             color: #17a2b8;
  500.         }
  501.         .custom-alert-modal .modal-body .alert-message {
  502.             font-size: 1.1rem;
  503.             color: #333;
  504.             margin-bottom: 1rem;
  505.             line-height: 1.6;
  506.         }
  507.         .custom-alert-modal .modal-footer {
  508.             border-top: none;
  509.             padding: 0.5rem 1.5rem 1.5rem;
  510.             justify-content: center;
  511.         }
  512.         .custom-alert-modal .modal-footer .btn {
  513.             min-width: 120px;
  514.             padding: 0.6rem 1.5rem;
  515.             border-radius: 8px;
  516.             font-weight: 500;
  517.         }
  518.         /* Responsive pour single-product */
  519.         @media(max-width: 991.98px) {
  520.             .product-gallery-modern {
  521.                 flex-direction: column;
  522.             }
  523.             .thumbnails-container {
  524.                 flex-direction: row;
  525.                 width: 100%;
  526.                 overflow-x: auto;
  527.                 padding: 10px 0;
  528.             }
  529.             .thumbnail-item {
  530.                 flex-shrink: 0;
  531.             }
  532.             .main-image-container {
  533.                 width: 100%;
  534.             }
  535.         }
  536.         @media(max-width: 768px) {
  537.             .product-gallery-modern {
  538.                 flex-direction: column;
  539.             }
  540.             .thumbnails-container {
  541.                 width: 100%;
  542.                 flex-direction: row;
  543.                 align-items: center;
  544.             }
  545.             .product-thumbnails {
  546.                 flex-direction: row;
  547.                 width: 100%;
  548.                 max-height: 100px;
  549.                 overflow-x: auto;
  550.                 overflow-y: hidden;
  551.             }
  552.             .thumbnail-item {
  553.                 flex-shrink: 0;
  554.             }
  555.             .thumbnail-nav-btn {
  556.                 margin: 0 5px;
  557.             }
  558.             .thumbnail-nav-up {
  559.                 order: 1;
  560.             }
  561.             .product-thumbnails {
  562.                 order: 2;
  563.             }
  564.             .thumbnail-nav-down {
  565.                 order: 3;
  566.             }
  567.             .image-overlay-icons {
  568.                 opacity: 1;
  569.             }
  570.         }
  571.     </style>
  572. {% endblock %}
  573. {% block body %}
  574.     <!-- Start Banner Area -->
  575.     <section class="banner-area organic-breadcrumb">
  576.         <div class="container">
  577.             <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  578.                 <div class="col-first">
  579.                     <h1>Product Details Page</h1>
  580.                     <nav class="d-flex align-items-center">
  581.                         <a href="index.html">Home<span class="lnr lnr-arrow-right"></span>
  582.                         </a>
  583.                         <a href="#">Shop<span class="lnr lnr-arrow-right"></span>
  584.                         </a>
  585.                         <a href="single-product.html">product-details</a>
  586.                     </nav>
  587.                 </div>
  588.             </div>
  589.         </div>
  590.     </section>
  591.     <!-- End Banner Area -->
  592.     <!--================Single Product Area =================-->
  593.     <div class="product_image_area">
  594.         <div class="container">
  595.             <div class="row s_product_inner">
  596.                 <div class="col-lg-6">
  597.                     <div
  598.                         class="product-gallery-modern">
  599.                         <!-- Colonne de miniatures à gauche avec navigation -->
  600.                         <div class="thumbnails-container">
  601.                             <button class="thumbnail-nav-btn thumbnail-nav-up" id="thumbnail-nav-up" title="Image précédente">
  602.                                 <i class="lnr lnr-chevron-up"></i>
  603.                             </button>
  604.                             <div class="product-thumbnails" id="product-thumbnails">
  605.                                 <div class="thumbnails-wrapper" id="thumbnails-wrapper">
  606.                                     {% if product.images is defined and product.images|length > 0 %}
  607.                                         {% for img in product.images %}
  608.                                             <div class="thumbnail-item {% if loop.first %}active permanently-active{% endif %}" data-image="{{ asset(img) }}" data-index="{{ loop.index0 }}">
  609.                                                 <img src="{{ asset(img) }}" alt="{{ product.name }} - Image {{ loop.index }}">
  610.                                             </div>
  611.                                         {% endfor %}
  612.                                     {% else %}
  613.                                         <div class="thumbnail-item active permanently-active" data-image="{{ asset('ui/img/category/s-p1.jpg') }}" data-index="0">
  614.                                             <img src="{{ asset('ui/img/category/s-p1.jpg') }}" alt="{{ product.name }}">
  615.                                         </div>
  616.                                     {% endif %}
  617.                                 </div>
  618.                             </div>
  619.                             <button class="thumbnail-nav-btn thumbnail-nav-down" id="thumbnail-nav-down" title="Image suivante">
  620.                                 <i class="lnr lnr-chevron-down"></i>
  621.                             </button>
  622.                         </div>
  623.                         <!-- Grande image principale -->
  624.                         <div class="product-main-image-wrapper">
  625.                             <div class="product-main-image">
  626.                                 {% if product.images is defined and product.images|length > 0 %}
  627.                                     <img id="main-product-image" src="{{ asset(product.images[0]) }}" alt="{{ product.name }}">
  628.                                 {% else %}
  629.                                     <img id="main-product-image" src="{{ asset('ui/img/category/s-p1.jpg') }}" alt="{{ product.name }}">
  630.                                 {% endif %}
  631.                             </div>
  632.                             <!-- Overlay avec icônes -->
  633.                             <div class="image-overlay-icons">
  634.                                 <button class="icon-btn zoom-btn" onclick="openImageZoom()" title="Agrandir l'image">
  635.                                     <i class="lnr lnr-magnifier"></i>
  636.                                 </button>
  637.                                 <button class="icon-btn favorite-btn" {% if app.user %} onclick="toggleWishlist({{ product.id }}, this); return false;" {% else %} onclick="showCustomAlert('Vous devez être connecté pour ajouter aux favoris', 'warning'); return false;" {% endif %} title="Ajouter aux favoris" data-product-id="{{ product.id }}">
  638.                                     <i class="lnr lnr-heart"></i>
  639.                                 </button>
  640.                             </div>
  641.                             <!-- Navigation sur l'image principale -->
  642.                             <div class="main-image-nav">
  643.                                 <button type="button" class="main-image-nav-btn" id="main-image-prev" title="Image précédente">
  644.                                     <i class="lnr lnr-chevron-left"></i>
  645.                                 </button>
  646.                                 <button type="button" class="main-image-nav-btn" id="main-image-next" title="Image suivante">
  647.                                     <i class="lnr lnr-chevron-right"></i>
  648.                                 </button>
  649.                             </div>
  650.                             <!-- Indicateur d'image active -->
  651.                             <div class="image-counter">
  652.                                 <span id="current-image-index">1</span>
  653.                                 /
  654.                                 <span id="total-images">{{ (product.images|length) ?: 1 }}</span>
  655.                             </div>
  656.                         </div>
  657.                     </div>
  658.                 </div>
  659.                 <div class="col-lg-5 offset-lg-1">
  660.                     <div class="s_product_text">
  661.                         <h3>{{ product.name }}</h3>
  662.                         <h2>
  663.                             <span id="main-unit-price">{{ product.price|number_format(2, '.', ' ') }}</span>
  664.                             HTG
  665.                         </h2>
  666.                         <ul class="list">
  667.                             <li>
  668.                                 <a class="active" href="#">
  669.                                     <span>Category</span>
  670.                                     :
  671.                                     {{ product.category.name }}</a>
  672.                             </li>
  673.                             <li>
  674.                                 <a href="#">
  675.                                     <span>Disponibilité</span>
  676.                                     :
  677.                                     {{ product.stockStatus }}</a>
  678.                             </li>
  679.                         </ul>
  680.                         <p>{{ product.description|raw|striptags|nl2br }}</p>
  681.                         <!-- Informations sur la boutique -->
  682.                         <div class="shop-info mb-3">
  683.                             <small class="text-muted">
  684.                                 <i class="lnr lnr-store"></i>
  685.                                 Vendu par :
  686.                                 {% if product.shop is defined and product.shop %}
  687.                                     <a href="{{ path('ui_shop_show', {'slug': product.shop.slug}) }}" class="shop-link">
  688.                                         {{ product.shop.name }}
  689.                                     </a>
  690.                                 {% else %}
  691.                                     <span class="text-muted">Boutique inconnue</span>
  692.                                 {% endif %}
  693.                             </small>
  694.                         </div>
  695.                         <div class="product_count">
  696.                             <label for="qty">Quantité :</label>
  697.                             <input type="text" name="qty" id="sst" maxlength="12" value="1" title="Quantité:" class="input-text qty" max="{{ product.stock ?? 99 }}">
  698.                             <button onclick="var result = document.getElementById('sst'); var sst = result.value; if( !isNaN( sst ) && sst < {{ product.stock ?? 99 }}) result.value++;return false;" class="increase items-count" type="button">
  699.                                 <i class="lnr lnr-chevron-up"></i>
  700.                             </button>
  701.                             <button onclick="var result = document.getElementById('sst'); var sst = result.value; if( !isNaN( sst ) && sst > 1 ) result.value--;return false;" class="reduced items-count" type="button">
  702.                                 <i class="lnr lnr-chevron-down"></i>
  703.                             </button>
  704.                         </div>
  705.                         <input
  706.                         type="hidden" id="unit-price-input" value="{{ product.price|number_format(2, '.', '') }}"/>
  707.                         {# Prix de gros (bulk pricing) #}
  708.                         {% set tierPrices = [] %}
  709.                         {% if product.tierPrices is defined and product.tierPrices|length > 0 %}
  710.                             {% for tier in product.tierPrices %}
  711.                                 {% set tierPrices = tierPrices|merge([{ 'min': tier.minQty, 'price': tier.price }]) %}
  712.                             {% endfor %}
  713.                         {% else %}
  714.                             {# Facultatif: exemple si pas de data backend #}
  715.                             {% set tierPrices = [
  716.                                 { 'min': 1, 'price': product.price },
  717.                                 { 'min': 5, 'price': (product.price * 0.98)|round(2, 'floor') },
  718.                                 { 'min': 10, 'price': (product.price * 0.95)|round(2, 'floor') },
  719.                                 { 'min': 20, 'price': (product.price * 0.92)|round(2, 'floor') }
  720.                             ] %}
  721.                         {% endif %}
  722.                         <div id="bulk-pricing" class="mt-3" data-tiers='{{ tierPrices|json_encode|e('html_attr') }}'>
  723.                             <div class="d-flex align-items-center mb-2">
  724.                                 <strong class="me-2">Prix unitaire selon quantité :</strong>
  725.                                 <span id="unit-price-value">{{ product.price|number_format(2, '.', ' ') }}
  726.                                     HTG</span>
  727.                             </div>
  728.                             <div class="table-responsive">
  729.                                 <table class="table table-sm mb-2" style="border:1px solid #eee;">
  730.                                     <thead class="table-light">
  731.                                         <tr>
  732.                                             <th>Quantité min.</th>
  733.                                             <th>Prix unitaire (HTG)</th>
  734.                                         </tr>
  735.                                     </thead>
  736.                                     <tbody id="bulk-tier-rows">
  737.                                         {% for tier in tierPrices|sort((a,b)=> a.min <=> b.min) %}
  738.                                             <tr data-min="{{ tier.min }}">
  739.                                                 <td>≥
  740.                                                     {{ tier.min }}</td>
  741.                                                 <td>{{ tier.price|number_format(2, '.', ' ') }}</td>
  742.                                             </tr>
  743.                                         {% endfor %}
  744.                                     </tbody>
  745.                                 </table>
  746.                             </div>
  747.                             <div class="d-flex flex-wrap gap-3 align-items-baseline mb-2">
  748.                                 <div>
  749.                                     <strong>Total:</strong>
  750.                                     <span id="total-price-value">{{ product.price|number_format(2, '.', ' ') }}</span>
  751.                                     HTG
  752.                                 </div>
  753.                                 <div class="text-success">
  754.                                     <strong>Économies:</strong>
  755.                                     <span id="savings-amount">0.00</span>
  756.                                     HTG
  757.                                                                                                                                                                                     (<span id="savings-percent">0</span>%)
  758.                                 </div>
  759.                             </div>
  760.                             <small class="text-muted">Le prix et le total s’adaptent automatiquement selon la quantité.</small>
  761.                         </div>
  762.                         <!-- Stock disponible -->
  763.                         {% if product.stock is defined %}
  764.                             <small class="text-muted">Stock disponible :
  765.                                 {{ product.stock }}
  766.                                 unités</small>
  767.                         {% endif %}
  768.                         <!-- Statistiques du produit -->
  769.                         {% if productStats is defined %}
  770.                             <div class="product-stats mt-3">
  771.                                 <div class="row text-center">
  772.                                     <div class="col-4">
  773.                                         <div class="stat-item">
  774.                                             <span class="stat-number">{{ productStats.totalViews }}</span>
  775.                                             <small class="stat-label">Vues</small>
  776.                                         </div>
  777.                                     </div>
  778.                                     <div class="col-4">
  779.                                         <div class="stat-item">
  780.                                             <span class="stat-number">{{ productStats.salesCount }}</span>
  781.                                             <small class="stat-label">Ventes</small>
  782.                                         </div>
  783.                                     </div>
  784.                                     <div class="col-4">
  785.                                         <div class="stat-item">
  786.                                             <span class="stat-number">{{ productStats.conversionRate }}%</span>
  787.                                             <small class="stat-label">Conversion</small>
  788.                                         </div>
  789.                                     </div>
  790.                                 </div>
  791.                             </div>
  792.                         {% endif %}
  793.                         <div class="card_area d-flex align-items-center">
  794.                             <a class="primary-btn btn-add-to-cart" href="javascript:void(0);" id="add-to-cart-btn" data-product-id="{{ product.id }}" data-qty="1">
  795.                                 <i class="lnr lnr-cart"></i>
  796.                                 Ajouter au panier
  797.                             </a>
  798.                             <a class="icon_btn wishlist-btn" href="#" title="Ajouter aux favoris" data-product-id="{{ product.id }}" {% if app.user %} onclick="toggleWishlist({{ product.id }}, this); return false;" {% else %} onclick="showCustomAlert('Vous devez être connecté pour ajouter aux favoris', 'warning'); return false;" {% endif %}>
  799.                                 <i class="lnr lnr-heart"></i>
  800.                             </a>
  801.                             <a class="icon_btn comparison-btn" href="#" title="Comparer" data-product-id="{{ product.id }}" onclick="toggleComparison({{ product.id }}, this); return false;">
  802.                                 <i class="lnr lnr-sync"></i>
  803.                             </a>
  804.                         </div>
  805.                         <!-- Message de confirmation -->
  806.                         <div id="cart-message" class="alert alert-success mt-3" style="display: none;">
  807.                             <i class="lnr lnr-checkmark-circle"></i>
  808.                             Produit ajouté au panier avec succès !
  809.                         </div>
  810.                         <!-- ... existing code ... -->
  811.                     </div>
  812.                 </div>
  813.             </div>
  814.         </div>
  815.     </div>
  816.     <!--================End Single Product Area =================-->
  817.     <!--================Product Description Area =================-->
  818.     <section class="product_description_area">
  819.         <div class="container">
  820.             <ul class="nav nav-tabs" id="myTab" role="tablist">
  821.                 <li class="nav-item">
  822.                     <a class="nav-link active" id="home-tab" data-bs-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Description</a>
  823.                 </li>
  824.                 <li class="nav-item">
  825.                     <a class="nav-link" id="profile-tab" data-bs-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Spécifications</a>
  826.                 </li>
  827.                 <li class="nav-item">
  828.                     <a class="nav-link" id="contact-tab" data-bs-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Commentaires</a>
  829.                 </li>
  830.                 <li class="nav-item">
  831.                     <a class="nav-link" id="review-tab" data-bs-toggle="tab" href="#review" role="tab" aria-controls="review" aria-selected="false">Avis ({{ product.reviewCount ?? 0 }})</a>
  832.                 </li>
  833.             </ul>
  834.             <div class="tab-content" id="myTabContent">
  835.                 <div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
  836.                     <div class="description">
  837.                         {% if product.description %}
  838.                             <div class="product-description-content">
  839.                                 {{ product.description|raw|nl2br }}
  840.                             </div>
  841.                         {% else %}
  842.                             <p class="text-muted">Aucune description disponible pour ce produit.</p>
  843.                         {% endif %}
  844.                     </div>
  845.                 </div>
  846.                 <div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">
  847.                     <div class="specification-table">
  848.                         {% if product.weight or product.length or product.width or product.height or product.attributes %}
  849.                             <div class="table-responsive">
  850.                                 <table class="table">
  851.                                     <tbody>
  852.                                         {% if product.sku %}
  853.                                             <tr>
  854.                                                 <td>
  855.                                                     <h5>Référence (SKU)</h5>
  856.                                                 </td>
  857.                                                 <td>
  858.                                                     <h5>{{ product.sku }}</h5>
  859.                                                 </td>
  860.                                             </tr>
  861.                                         {% endif %}
  862.                                         {% if product.barcode %}
  863.                                             <tr>
  864.                                                 <td>
  865.                                                     <h5>Code-barres</h5>
  866.                                                 </td>
  867.                                                 <td>
  868.                                                     <h5>{{ product.barcode }}</h5>
  869.                                                 </td>
  870.                                             </tr>
  871.                                         {% endif %}
  872.                                         {% if product.brand %}
  873.                                             <tr>
  874.                                                 <td>
  875.                                                     <h5>Marque</h5>
  876.                                                 </td>
  877.                                                 <td>
  878.                                                     <h5>{{ product.brand.name }}</h5>
  879.                                                 </td>
  880.                                             </tr>
  881.                                         {% endif %}
  882.                                         {% if product.category %}
  883.                                             <tr>
  884.                                                 <td>
  885.                                                     <h5>Catégorie</h5>
  886.                                                 </td>
  887.                                                 <td>
  888.                                                     <h5>{{ product.category.name }}</h5>
  889.                                                 </td>
  890.                                             </tr>
  891.                                         {% endif %}
  892.                                         {% if product.weight %}
  893.                                             <tr>
  894.                                                 <td>
  895.                                                     <h5>Poids</h5>
  896.                                                 </td>
  897.                                                 <td>
  898.                                                     <h5>{{ product.weight }}
  899.                                                         kg</h5>
  900.                                                 </td>
  901.                                             </tr>
  902.                                         {% endif %}
  903.                                         {% if product.length or product.width or product.height %}
  904.                                             <tr>
  905.                                                 <td>
  906.                                                     <h5>Dimensions</h5>
  907.                                                 </td>
  908.                                                 <td>
  909.                                                     <h5>
  910.                                                         {% if product.length %}
  911.                                                             {{ product.length }}cm
  912.                                                         {% endif %}
  913.                                                         {% if product.width %}
  914.                                                             ×
  915.                                                             {{ product.width }}cm
  916.                                                         {% endif %}
  917.                                                         {% if product.height %}
  918.                                                             ×
  919.                                                             {{ product.height }}cm
  920.                                                         {% endif %}
  921.                                                     </h5>
  922.                                                 </td>
  923.                                             </tr>
  924.                                         {% endif %}
  925.                                         {% if product.stock is not null %}
  926.                                             <tr>
  927.                                                 <td>
  928.                                                     <h5>Stock disponible</h5>
  929.                                                 </td>
  930.                                                 <td>
  931.                                                     <h5>{{ product.stock }}
  932.                                                         unité(s)</h5>
  933.                                                 </td>
  934.                                             </tr>
  935.                                         {% endif %}
  936.                                         {% if product.stockStatus %}
  937.                                             <tr>
  938.                                                 <td>
  939.                                                     <h5>Statut</h5>
  940.                                                 </td>
  941.                                                 <td>
  942.                                                     <h5>
  943.                                                         {% if product.stockStatus == 'in_stock' %}
  944.                                                             <span class="badge bg-success">En stock</span>
  945.                                                         {% elseif product.stockStatus == 'out_of_stock' %}
  946.                                                             <span class="badge bg-danger">Rupture de stock</span>
  947.                                                         {% elseif product.stockStatus == 'backorder' %}
  948.                                                             <span class="badge bg-warning">Sur commande</span>
  949.                                                         {% else %}
  950.                                                             {{ product.stockStatus }}
  951.                                                         {% endif %}
  952.                                                     </h5>
  953.                                                 </td>
  954.                                             </tr>
  955.                                         {% endif %}
  956.                                         {% if product.isDigital %}
  957.                                             <tr>
  958.                                                 <td>
  959.                                                     <h5>Type</h5>
  960.                                                 </td>
  961.                                                 <td>
  962.                                                     <h5>
  963.                                                         <span class="badge bg-info">Produit digital</span>
  964.                                                     </h5>
  965.                                                 </td>
  966.                                             </tr>
  967.                                         {% endif %}
  968.                                         {% if product.attributes is not empty %}
  969.                                             {% for key, value in product.attributes %}
  970.                                                 <tr>
  971.                                                     <td>
  972.                                                         <h5>{{ key|replace({'_': ' '})|title }}</h5>
  973.                                                     </td>
  974.                                                     <td>
  975.                                                         <h5>{{ value }}</h5>
  976.                                                     </td>
  977.                                                 </tr>
  978.                                             {% endfor %}
  979.                                         {% endif %}
  980.                                     </tbody>
  981.                                 </table>
  982.                             </div>
  983.                         {% else %}
  984.                             <p class="text-muted">Aucune spécification disponible pour ce produit.</p>
  985.                         {% endif %}
  986.                     </div>
  987.                 </div>
  988.                 <div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
  989.                     <div class="comment-wrapper">
  990.                         <div class="alert alert-info">
  991.                             <i class="lnr lnr-info-circle"></i>
  992.                             La fonctionnalité de commentaires sera bientôt disponible. Vous pourrez laisser des commentaires et poser des questions sur ce produit.
  993.                         </div>
  994.                         {% if app.user %}
  995.                             <div class="review_box mt-4">
  996.                                 <h4>Poser une question</h4>
  997.                                 <form class="row contact_form" method="post" novalidate="novalidate">
  998.                                     <div class="col-md-12">
  999.                                         <div class="form-group">
  1000.                                             <textarea class="form-control" name="comment" id="comment" rows="3" placeholder="Votre question ou commentaire..." required></textarea>
  1001.                                         </div>
  1002.                                     </div>
  1003.                                     <div class="col-md-12 text-right">
  1004.                                         <button type="submit" class="btn primary-btn">Envoyer</button>
  1005.                                     </div>
  1006.                                 </form>
  1007.                             </div>
  1008.                         {% else %}
  1009.                             <p class="text-center">
  1010.                                 <a href="{{ path('ui_app_login') }}" class="primary-btn">Connectez-vous</a>
  1011.                                 pour poser une question ou laisser un commentaire.
  1012.                             </p>
  1013.                         {% endif %}
  1014.                     </div>
  1015.                 </div>
  1016.                 <div class="tab-pane fade" id="review" role="tabpanel" aria-labelledby="review-tab">
  1017.                     <div class="review-wrapper">
  1018.                         <div class="row">
  1019.                             <div class="col-lg-6">
  1020.                                 <div class="row total_rate">
  1021.                                     <div class="col-6">
  1022.                                         <div class="box_total">
  1023.                                             <h5>Note globale</h5>
  1024.                                             <h4>{{ product.averageRating ? product.averageRating|number_format(1, '.', ',') : '0.0' }}</h4>
  1025.                                             <h6>({{ product.reviewCount ?? 0 }}
  1026.                                                 Avis)</h6>
  1027.                                         </div>
  1028.                                     </div>
  1029.                                     <div class="col-6">
  1030.                                         <div class="rating_list">
  1031.                                             <h3>Basé sur
  1032.                                                 {{ product.reviewCount ?? 0 }}
  1033.                                                 avis</h3>
  1034.                                             <div class="rating-summary">
  1035.                                                 {% if product.averageRating %}
  1036.                                                     <div class="rating-display mb-3">
  1037.                                                         {% for i in 1..5 %}
  1038.                                                             <i class="fa fa-star{% if i <= product.averageRating %}{% else %}-o{% endif %}" style="color: #ffa200; font-size: 1.2rem;"></i>
  1039.                                                         {% endfor %}
  1040.                                                         <span class="ms-2" style="font-size: 1.1rem; font-weight: bold;">{{ product.averageRating|number_format(1, '.', ',') }}
  1041.                                                             / 5.0</span>
  1042.                                                     </div>
  1043.                                                 {% else %}
  1044.                                                     <p class="text-muted">Aucune note disponible</p>
  1045.                                                 {% endif %}
  1046.                                             </div>
  1047.                                         </div>
  1048.                                     </div>
  1049.                                 </div>
  1050.                                 <div class="review_list">
  1051.                                     {% if product.reviewCount and product.reviewCount > 0 %}
  1052.                                         <div class="alert alert-info">
  1053.                                             <i class="lnr lnr-info-circle"></i>
  1054.                                             Le système d'affichage détaillé des avis sera disponible prochainement. 
  1055.                                                                                                                                                                                                                                                                         Note actuelle :
  1056.                                             {{ product.averageRating|number_format(1, '.', ',') }}
  1057.                                             / 5.0 basée sur
  1058.                                             {{ product.reviewCount }}
  1059.                                             avis.
  1060.                                         </div>
  1061.                                     {% else %}
  1062.                                         <div class="alert alert-info">
  1063.                                             <i class="lnr lnr-info-circle"></i>
  1064.                                             Aucun avis pour ce produit pour le moment. Soyez le premier à laisser un avis après votre achat !
  1065.                                         </div>
  1066.                                     {% endif %}
  1067.                                 </div>
  1068.                             </div>
  1069.                             <div class="col-lg-6">
  1070.                                 <div class="review_box">
  1071.                                     <h4>Laisser un avis</h4>
  1072.                                     {% if app.user %}
  1073.                                         <p class="text-muted">Vous pouvez laisser un avis après avoir acheté ce produit.</p>
  1074.                                         <form class="row contact_form" method="post" novalidate="novalidate">
  1075.                                             <div class="col-md-12">
  1076.                                                 <div class="form-group">
  1077.                                                     <label>Votre note</label>
  1078.                                                     <div class="rating-input mb-3" style="display: flex; gap: 5px; flex-direction: row-reverse; justify-content: flex-end;">
  1079.                                                         {% for i in 5..1 %}
  1080.                                                             <input type="radio" name="rating" id="rating{{ i }}" value="{{ i }}" required style="display: none;">
  1081.                                                             <label for="rating{{ i }}" class="star-label" style="cursor: pointer;">
  1082.                                                                 <i class="fa fa-star-o" style="color: #ccc; font-size: 1.5rem;"></i>
  1083.                                                             </label>
  1084.                                                         {% endfor %}
  1085.                                                     </div>
  1086.                                                 </div>
  1087.                                             </div>
  1088.                                             <div class="col-md-12">
  1089.                                                 <div class="form-group">
  1090.                                                     <textarea class="form-control" name="review" id="review" rows="5" placeholder="Votre avis..." required onfocus="this.placeholder = ''" onblur="this.placeholder = 'Votre avis...'"></textarea>
  1091.                                                 </div>
  1092.                                             </div>
  1093.                                             <div class="col-md-12 text-right">
  1094.                                                 <button type="submit" class="btn primary-btn">Publier l'avis</button>
  1095.                                             </div>
  1096.                                         </form>
  1097.                                     {% else %}
  1098.                                         <p class="text-center">
  1099.                                             <a href="{{ path('ui_app_login') }}" class="primary-btn">Connectez-vous</a>
  1100.                                             pour laisser un avis sur ce produit.
  1101.                                         </p>
  1102.                                     {% endif %}
  1103.                                 </div>
  1104.                             </div>
  1105.                         </div>
  1106.                     </div>
  1107.                 </div>
  1108.             </div>
  1109.         </div>
  1110.     </section>
  1111.     <!--================End Product Description Area =================-->
  1112.     <script>
  1113.         // Initialiser les onglets Bootstrap
  1114. document.addEventListener('DOMContentLoaded', function () { // Activer les onglets Bootstrap 5
  1115. const triggerTabList = document.querySelectorAll('#myTab button[data-bs-toggle="tab"]');
  1116. triggerTabList.forEach(triggerEl => {
  1117. const tabTrigger = new bootstrap.Tab(triggerEl);
  1118. triggerEl.addEventListener('click', event => {
  1119. event.preventDefault();
  1120. tabTrigger.show();
  1121. });
  1122. });
  1123. // Gestion des étoiles pour les avis
  1124. const ratingInputs = document.querySelectorAll('.rating-input input[type="radio"]');
  1125. const starLabels = document.querySelectorAll('.rating-input .star-label');
  1126. ratingInputs.forEach((input, index) => {
  1127. input.addEventListener('change', function () {
  1128. const rating = parseInt(this.value);
  1129. starLabels.forEach((label) => { // Trouver l'input associé à ce label
  1130. const labelInput = label.previousElementSibling;
  1131. if (labelInput && labelInput.type === 'radio') {
  1132. const starValue = parseInt(labelInput.value);
  1133. const star = label.querySelector('i');
  1134. if (starValue <= rating) {
  1135. star.style.color = '#ffa200';
  1136. star.classList.remove('fa-star-o');
  1137. star.classList.add('fa-star');
  1138. } else {
  1139. star.style.color = '#ccc';
  1140. star.classList.remove('fa-star');
  1141. star.classList.add('fa-star-o');
  1142. }
  1143. }
  1144. });
  1145. });
  1146. });
  1147. // Pré-remplir les étoiles au survol
  1148. starLabels.forEach((label) => {
  1149. label.addEventListener('mouseenter', function () { // Trouver l'input associé à ce label
  1150. const input = label.previousElementSibling;
  1151. if (input && input.type === 'radio') {
  1152. const rating = parseInt(input.value);
  1153. starLabels.forEach((l) => {
  1154. const lInput = l.previousElementSibling;
  1155. if (lInput && lInput.type === 'radio') {
  1156. const starValue = parseInt(lInput.value);
  1157. const star = l.querySelector('i');
  1158. if (starValue <= rating) {
  1159. star.style.color = '#ffa200';
  1160. star.classList.remove('fa-star-o');
  1161. star.classList.add('fa-star');
  1162. } else {
  1163. star.style.color = '#ccc';
  1164. star.classList.remove('fa-star');
  1165. star.classList.add('fa-star-o');
  1166. }
  1167. }
  1168. });
  1169. }
  1170. });
  1171. });
  1172. const ratingContainer = document.querySelector('.rating-input');
  1173. if (ratingContainer) {
  1174. ratingContainer.addEventListener('mouseleave', function () {
  1175. const checkedInput = document.querySelector('.rating-input input[type="radio"]:checked');
  1176. if (checkedInput) {
  1177. checkedInput.dispatchEvent(new Event('change'));
  1178. } else {
  1179. starLabels.forEach(label => {
  1180. const star = label.querySelector('i');
  1181. star.style.color = '#ccc';
  1182. });
  1183. }
  1184. });
  1185. }
  1186. });
  1187.     </script>
  1188.     {% if youMightAlsoLike|length > 0 %}
  1189.         <section class="related-product-area section_gap_bottom py-5" style="background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);">
  1190.             <div class="container">
  1191.                 <div class="row justify-content-center">
  1192.                     <div class="col-lg-8 text-center">
  1193.                         <div class="section-title">
  1194.                             <h2 class="fw-bold mb-3" style="font-size: 2.5rem; color: #2c3e50;">Vous pourriez aussi aimer</h2>
  1195.                             <p class="text-muted" style="font-size: 1.1rem;">Produits sélectionnés pour vous en fonction de vos préférences</p>
  1196.                         </div>
  1197.                     </div>
  1198.                 </div>
  1199.                 <div class="row g-4">
  1200.                     {% for relatedProduct in youMightAlsoLike %}
  1201.                         <div class="col-xl-3 col-lg-4 col-md-6 col-sm-6 mb-4 d-flex align-items-stretch">
  1202.                             {% include 'home/_single_product_card.html.twig' with {'product': relatedProduct} %}
  1203.                         </div>
  1204.                     {% endfor %}
  1205.                 </div>
  1206.             </div>
  1207.         </section>
  1208.         <style>
  1209.             .modern-product-card {
  1210.                 background: #ffffff;
  1211.                 border-radius: 16px;
  1212.                 overflow: hidden;
  1213.                 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  1214.                 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  1215.                 height: 100%;
  1216.                 display: flex;
  1217.                 flex-direction: column;
  1218.                 position: relative;
  1219.             }
  1220.             .modern-product-card:hover {
  1221.                 transform: translateY(-8px);
  1222.                 box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
  1223.             }
  1224.             .product-image-wrapper {
  1225.                 position: relative;
  1226.                 width: 100%;
  1227.                 height: 250px;
  1228.                 overflow: hidden;
  1229.                 background: #f8f9fa;
  1230.             }
  1231.             .product-image {
  1232.                 width: 100%;
  1233.                 height: 100%;
  1234.                 object-fit: cover;
  1235.                 transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  1236.             }
  1237.             .modern-product-card:hover .product-image {
  1238.                 transform: scale(1.1);
  1239.             }
  1240.             .product-overlay {
  1241.                 position: absolute;
  1242.                 top: 0;
  1243.                 left: 0;
  1244.                 right: 0;
  1245.                 bottom: 0;
  1246.                 background: rgba(0, 0, 0, 0.5);
  1247.                 display: flex;
  1248.                 align-items: center;
  1249.                 justify-content: center;
  1250.                 opacity: 0;
  1251.                 transition: opacity 0.3s ease;
  1252.             }
  1253.             .modern-product-card:hover .product-overlay {
  1254.                 opacity: 1;
  1255.             }
  1256.             .view-product-btn {
  1257.                 color: white;
  1258.                 padding: 12px 24px;
  1259.                 background: rgba(255, 255, 255, 0.2);
  1260.                 backdrop-filter: blur(10px);
  1261.                 border-radius: 8px;
  1262.                 font-weight: 600;
  1263.                 display: inline-flex;
  1264.                 align-items: center;
  1265.                 gap: 8px;
  1266.                 transition: all 0.3s ease;
  1267.             }
  1268.             .view-product-btn:hover {
  1269.                 background: rgba(255, 255, 255, 0.3);
  1270.                 transform: scale(1.05);
  1271.             }
  1272.             .product-badge {
  1273.                 position: absolute;
  1274.                 top: 12px;
  1275.                 right: 12px;
  1276.                 z-index: 2;
  1277.             }
  1278.             .badge-discount {
  1279.                 background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
  1280.                 color: white;
  1281.                 padding: 6px 12px;
  1282.                 border-radius: 20px;
  1283.                 font-size: 0.75rem;
  1284.                 font-weight: 700;
  1285.                 box-shadow: 0 2px 8px rgba(238, 90, 111, 0.4);
  1286.             }
  1287.             .product-info {
  1288.                 padding: 20px;
  1289.                 flex-grow: 1;
  1290.                 display: flex;
  1291.                 flex-direction: column;
  1292.             }
  1293.             .product-title {
  1294.                 margin: 0 0 8px 0;
  1295.                 font-size: 1rem;
  1296.                 font-weight: 600;
  1297.                 line-height: 1.4;
  1298.                 height: 2.8em;
  1299.                 overflow: hidden;
  1300.                 display: -webkit-box;
  1301.                 -webkit-line-clamp: 2;
  1302.                 -webkit-box-orient: vertical;
  1303.             }
  1304.             .product-title a {
  1305.                 color: #2c3e50;
  1306.                 text-decoration: none;
  1307.                 transition: color 0.3s ease;
  1308.             }
  1309.             .product-title a:hover {
  1310.                 color: #ffa200;
  1311.             }
  1312.             .product-shop {
  1313.                 font-size: 0.85rem;
  1314.                 color: #6c757d;
  1315.                 margin-bottom: 12px;
  1316.                 display: flex;
  1317.                 align-items: center;
  1318.                 gap: 6px;
  1319.             }
  1320.             .product-shop i {
  1321.                 font-size: 0.9rem;
  1322.             }
  1323.             .shop-link {
  1324.                 color: #6c757d;
  1325.                 text-decoration: none;
  1326.                 transition: color 0.3s ease;
  1327.             }
  1328.             .shop-link:hover {
  1329.                 color: #ffa200;
  1330.             }
  1331.             .product-price-wrapper {
  1332.                 margin-bottom: 16px;
  1333.                 display: flex;
  1334.                 align-items: center;
  1335.                 gap: 8px;
  1336.                 flex-wrap: wrap;
  1337.             }
  1338.             .product-price {
  1339.                 font-size: 1.25rem;
  1340.                 font-weight: 700;
  1341.                 color: #ffa200;
  1342.             }
  1343.             .product-compare-price {
  1344.                 font-size: 0.9rem;
  1345.                 color: #adb5bd;
  1346.                 text-decoration: line-through;
  1347.             }
  1348.             .product-actions {
  1349.                 margin-top: auto;
  1350.             }
  1351.             /* Styles pour les boutons de la section "Vous pourriez aussi aimer" uniquement */
  1352.             .modern-product-card .btn-add-to-cart {
  1353.                 width: 100%;
  1354.                 padding: 12px;
  1355.                 background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%);
  1356.                 color: white;
  1357.                 border: none;
  1358.                 border-radius: 10px;
  1359.                 font-weight: 600;
  1360.                 display: flex;
  1361.                 align-items: center;
  1362.                 justify-content: center;
  1363.                 gap: 8px;
  1364.                 text-decoration: none;
  1365.                 transition: all 0.3s ease;
  1366.                 cursor: pointer;
  1367.             }
  1368.             .modern-product-card .btn-add-to-cart:hover {
  1369.                 background: linear-gradient(135deg, #e8910a 0%, #d6820a 100%);
  1370.                 transform: translateY(-2px);
  1371.                 box-shadow: 0 4px 12px rgba(255, 162, 0, 0.4);
  1372.                 color: white;
  1373.             }
  1374.             /* Le bouton principal garde son design original */
  1375.             .card_area .btn-add-to-cart {
  1376.                 width: auto;
  1377.                 display: inline-flex;
  1378.             }
  1379.             .modern-product-card .btn-add-to-cart:active {
  1380.                 transform: translateY(0);
  1381.             }
  1382.             .modern-product-card .btn-add-to-cart i {
  1383.                 font-size: 1.1rem;
  1384.             }
  1385.             /* Responsive adjustments */
  1386.             @media (max-width: 768px) {
  1387.                 .product-image-wrapper {
  1388.                     height: 200px;
  1389.                 }
  1390.                 .product-info {
  1391.                     padding: 16px;
  1392.                 }
  1393.                 .product-title {
  1394.                     font-size: 0.95rem;
  1395.                 }
  1396.                 .product-price {
  1397.                     font-size: 1.1rem;
  1398.                 }
  1399.                 .section-title h2 {
  1400.                     font-size: 2rem !important;
  1401.                 }
  1402.             }
  1403.             @media (max-width: 576px) {
  1404.                 .product-image-wrapper {
  1405.                     height: 190px;
  1406.                 }
  1407.                 .product-info {
  1408.                     padding: 14px;
  1409.                 }
  1410.                 .btn-add-to-cart {
  1411.                     padding: 10px;
  1412.                     font-size: 0.9rem;
  1413.                 }
  1414.             }
  1415.             /* Ensure fixed height for cards */
  1416.             .col-6.col-md-4.col-lg-3 {
  1417.                 display: flex;
  1418.             }
  1419.             .modern-product-card {
  1420.                 width: 100%;
  1421.             }
  1422.             /* Loading state for add to cart button */
  1423.             .btn-add-to-cart.loading {
  1424.                 opacity: 0.7;
  1425.                 pointer-events: none;
  1426.             }
  1427.             .btn-add-to-cart.loading i {
  1428.                 animation: spin 1s linear infinite;
  1429.             }
  1430.             @keyframes spin {
  1431.                 from { transform: rotate(0deg); }
  1432.                 to { transform: rotate(360deg); }
  1433.             }
  1434.             /* Success state - vert pour tous les boutons */
  1435.             .btn-add-to-cart.success {
  1436.                 background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
  1437.             }
  1438.             /* Le bouton principal garde son style original même en success */
  1439.             .card_area .btn-add-to-cart.success {
  1440.                 background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
  1441.             }
  1442.         </style>
  1443.         <script>
  1444.             // Gestion des boutons d'ajout au panier (principal et section "Vous pourriez aussi aimer")
  1445.             document.addEventListener('DOMContentLoaded', function() {
  1446.                 const addToCartButtons = document.querySelectorAll('.btn-add-to-cart');
  1447.                 
  1448.                 addToCartButtons.forEach(button => {
  1449.                     button.addEventListener('click', function(e) {
  1450.                         e.preventDefault();
  1451.                         e.stopPropagation();
  1452.                         
  1453.                         const productId = this.getAttribute('data-product-id');
  1454.                         
  1455.                         // Pour le bouton principal, récupérer la quantité depuis l'input sst si disponible
  1456.                         let qty = this.getAttribute('data-qty') || '1';
  1457.                         if (this.id === 'add-to-cart-btn') {
  1458.                             const qtyInput = document.getElementById('sst');
  1459.                             if (qtyInput) {
  1460.                                 qty = parseInt(qtyInput.value) || 1;
  1461.                             }
  1462.                         }
  1463.                         
  1464.                         if (!productId) {
  1465.                             console.error('Product ID manquant');
  1466.                             return false;
  1467.                         }
  1468.                         // Sauvegarder le contenu original
  1469.                         const originalContent = this.innerHTML;
  1470.                         
  1471.                         // Désactiver le bouton et afficher le loading
  1472.                         this.classList.add('loading');
  1473.                         if (this.id === 'add-to-cart-btn') {
  1474.                             this.style.pointerEvents = 'none';
  1475.                             this.innerHTML = '<i class="lnr lnr-spinner"></i> Ajout en cours...';
  1476.                         } else {
  1477.                             this.innerHTML = '<i class="ti ti-loader-2"></i> <span>Ajout...</span>';
  1478.                         }
  1479.                         
  1480.                         // Construire le body de la requête
  1481.                         let body = 'productId=' + encodeURIComponent(productId) + '&qty=' + encodeURIComponent(qty);
  1482.                         
  1483.                         // Pour le bouton principal, vérifier aussi le prix unitaire pour les prix de gros
  1484.                         if (this.id === 'add-to-cart-btn') {
  1485.                             const unitPriceInput = document.getElementById('unit-price-input');
  1486.                             const unitPrice = unitPriceInput ? unitPriceInput.value : '';
  1487.                             if (unitPrice) {
  1488.                                 body += '&unitPrice=' + encodeURIComponent(unitPrice);
  1489.                             }
  1490.                         }
  1491.                         
  1492.                         // Faire la requête AJAX
  1493.                         fetch('{{ path("ui_cart_add") }}', {
  1494.                             method: 'POST',
  1495.                             headers: {
  1496.                                 'Content-Type': 'application/x-www-form-urlencoded',
  1497.                                 'X-Requested-With': 'XMLHttpRequest'
  1498.                             },
  1499.                             body: body
  1500.                         })
  1501.                         .then(response => {
  1502.                             if (!response.ok) {
  1503.                                 throw new Error('Erreur réseau: ' + response.status);
  1504.                             }
  1505.                             return response.json();
  1506.                         })
  1507.                         .then(data => {
  1508.                             if (data.ok || data.success) {
  1509.                                 // Succès - Afficher l'animation de succès
  1510.                                 this.classList.remove('loading');
  1511.                                 this.classList.add('success');
  1512.                                 
  1513.                                 if (this.id === 'add-to-cart-btn') {
  1514.                                     this.style.background = 'linear-gradient(135deg, #28a745 0%, #20c997 100%)';
  1515.                                     this.innerHTML = '<i class="lnr lnr-checkmark-circle"></i> Ajouté !';
  1516.                                     
  1517.                                     // Afficher le message de confirmation si présent
  1518.                                     const cartMessage = document.getElementById('cart-message');
  1519.                                     if (cartMessage) {
  1520.                                         cartMessage.style.display = 'block';
  1521.                                         setTimeout(() => {
  1522.                                             cartMessage.style.display = 'none';
  1523.                                         }, 3000);
  1524.                                     }
  1525.                                 } else {
  1526.                                     this.innerHTML = '<i class="ti ti-check"></i> <span>Ajouté !</span>';
  1527.                                 }
  1528.                                 
  1529.                                 // Mettre à jour le compteur du panier si présent
  1530.                                 const cartBadge = document.querySelector('.cart-badge');
  1531.                                 if (cartBadge) {
  1532.                                     cartBadge.textContent = data.totalQty || data.quantity || 0;
  1533.                                     // Animation du badge
  1534.                                     cartBadge.style.transform = 'scale(1.2)';
  1535.                                     setTimeout(() => {
  1536.                                         cartBadge.style.transform = 'scale(1)';
  1537.                                     }, 300);
  1538.                                 }
  1539.                                 
  1540.                                 // Afficher une notification si la fonction existe
  1541.                                 if (typeof showCustomAlert === 'function') {
  1542.                                     showCustomAlert('Produit ajouté au panier avec succès !', 'success');
  1543.                                 }
  1544.                                 
  1545.                                 // Restaurer le bouton après 2 secondes
  1546.                                 setTimeout(() => {
  1547.                                     this.classList.remove('success');
  1548.                                     if (this.id === 'add-to-cart-btn') {
  1549.                                         this.style.background = '';
  1550.                                         this.style.pointerEvents = 'auto';
  1551.                                     }
  1552.                                     this.innerHTML = originalContent;
  1553.                                 }, 2000);
  1554.                             } else {
  1555.                                 // Erreur
  1556.                                 this.classList.remove('loading');
  1557.                                 if (this.id === 'add-to-cart-btn') {
  1558.                                     this.style.pointerEvents = 'auto';
  1559.                                 }
  1560.                                 this.innerHTML = originalContent;
  1561.                                 
  1562.                                 const message = data.message || 'Erreur lors de l\'ajout au panier';
  1563.                                 if (typeof showCustomAlert === 'function') {
  1564.                                     showCustomAlert(message, 'error');
  1565.                                 } else {
  1566.                                     alert(message);
  1567.                                 }
  1568.                             }
  1569.                         })
  1570.                         .catch(error => {
  1571.                             console.error('Erreur:', error);
  1572.                             this.classList.remove('loading');
  1573.                             if (this.id === 'add-to-cart-btn') {
  1574.                                 this.style.pointerEvents = 'auto';
  1575.                             }
  1576.                             this.innerHTML = originalContent;
  1577.                             
  1578.                             if (typeof showCustomAlert === 'function') {
  1579.                                 showCustomAlert('Erreur lors de l\'ajout au panier. Veuillez réessayer.', 'error');
  1580.                             } else {
  1581.                                 alert('Erreur lors de l\'ajout au panier. Veuillez réessayer.');
  1582.                             }
  1583.                         });
  1584.                         
  1585.                         return false;
  1586.                     });
  1587.                 });
  1588.             });
  1589.         </script>
  1590.     {% endif %}
  1591.     <script src="js/vendor/jquery-2.2.4.min.js"></script>
  1592.     <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
  1593.     <script src="js/vendor/bootstrap.min.js"></script>
  1594.     <script src="js/jquery.ajaxchimp.min.js"></script>
  1595.     <script src="js/jquery.nice-select.min.js"></script>
  1596.     <script src="js/jquery.sticky.js"></script>
  1597.     <script src="js/nouislider.min.js"></script>
  1598.     <script src="js/jquery.magnific-popup.min.js"></script>
  1599.     <script src="js/owl.carousel.min.js"></script>
  1600.     <!--gmaps Js-->
  1601.     <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCjCGmQ0Uq4exrzdcL6rvxywDDOvfAu6eE"></script>
  1602.     <script src="js/gmaps.min.js"></script>
  1603.     <script src="js/main.js"></script>
  1604.     <script>
  1605.         // Le gestionnaire d'événements pour les boutons d'ajout au panier est déjà défini
  1606.         // dans la section "Vous pourriez aussi aimer" et gère maintenant aussi le bouton principal
  1607. // Gestion de la galerie d'images
  1608. document.addEventListener('DOMContentLoaded', function () {
  1609. const thumbnails = document.querySelectorAll('.thumbnail-item');
  1610. const mainImage = document.getElementById('main-product-image');
  1611. const currentIndex = document.getElementById('current-image-index');
  1612. const totalImages = document.getElementById('total-images');
  1613. // Fonction pour changer l'image principale
  1614. function changeMainImage(thumbnail, index) {
  1615. const imageUrl = thumbnail.getAttribute('data-image');
  1616. if (mainImage && imageUrl) {
  1617. mainImage.style.opacity = '0';
  1618. setTimeout(() => {
  1619. mainImage.src = imageUrl;
  1620. mainImage.style.opacity = '1';
  1621. // Reset zoom
  1622. mainImage.style.transform = 'scale(1)';
  1623. mainImage.style.transformOrigin = 'center center';
  1624. }, 150);
  1625. }
  1626. // Mettre à jour l'indicateur
  1627. if (currentIndex) {
  1628. currentIndex.textContent = index + 1;
  1629. }
  1630. }
  1631. // Ajouter le zoom sur l'image principale au survol
  1632. const mainImageContainer = document.querySelector('.product-main-image-wrapper');
  1633. if (mainImageContainer && mainImage) {
  1634.     mainImageContainer.addEventListener('mousemove', function(e) {
  1635.         const rect = mainImageContainer.getBoundingClientRect();
  1636.         const x = e.clientX - rect.left;
  1637.         const y = e.clientY - rect.top;
  1638.         
  1639.         const xPercent = (x / rect.width) * 100;
  1640.         const yPercent = (y / rect.height) * 100;
  1641.         
  1642.         mainImage.style.transformOrigin = `${xPercent}% ${yPercent}%`;
  1643.         mainImage.style.transform = 'scale(2.2)'; // Zoom Alibaba-style
  1644.     });
  1645.     
  1646.     mainImageContainer.addEventListener('mouseleave', function() {
  1647.         mainImage.style.transformOrigin = 'center center';
  1648.         mainImage.style.transform = 'scale(1)';
  1649.     });
  1650. }
  1651. // Gérer le survol sur les miniatures (sélection permanente au survol)
  1652. thumbnails.forEach((thumbnail, index) => {
  1653. thumbnail.addEventListener('mouseenter', function () { // Retirer toutes les sélections permanentes
  1654. thumbnails.forEach(t => {
  1655. t.classList.remove('active', 'permanently-active');
  1656. });
  1657. // Ajouter active et permanently-active à la miniature survolée
  1658. thumbnail.classList.add('active', 'permanently-active');
  1659. // Changer l'image principale
  1660. changeMainImage(thumbnail, index);
  1661. });
  1662. });
  1663. // Gérer le clic sur les miniatures (sélection permanente)
  1664. thumbnails.forEach((thumbnail, index) => {
  1665. thumbnail.addEventListener('click', function () { // Retirer la classe active et permanently-active de toutes les miniatures
  1666. thumbnails.forEach(t => {
  1667. t.classList.remove('active', 'permanently-active');
  1668. });
  1669. // Ajouter les classes active et permanently-active à la miniature cliquée
  1670. thumbnail.classList.add('active', 'permanently-active');
  1671. // Changer l'image principale
  1672. changeMainImage(thumbnail, index);
  1673. });
  1674. });
  1675. // Gestion du carrousel de miniatures avec taille fixe carrée
  1676. const navUpBtn = document.getElementById('thumbnail-nav-up');
  1677. const navDownBtn = document.getElementById('thumbnail-nav-down');
  1678. const thumbnailsContainer = document.getElementById('product-thumbnails');
  1679. const thumbnailsWrapper = document.getElementById('thumbnails-wrapper');
  1680. const thumbnailItems = Array.from(document.querySelectorAll('.thumbnail-item'));
  1681. let currentThumbnailOffset = 0;
  1682. const thumbnailHeight = 90;
  1683. // 80px (hauteur) + 10px (gap)
  1684. // Calculer la hauteur disponible pour les miniatures dynamiquement
  1685. const thumbnailsEl = document.getElementById('product-thumbnails');
  1686. let containerHeight = thumbnailsEl ? thumbnailsEl.clientHeight : 520; // Hauteur par défaut si non disponible
  1687. let visibleThumbnails = Math.floor(containerHeight / thumbnailHeight);
  1688. // Recalculer après le chargement pour avoir la vraie hauteur
  1689. setTimeout(() => {
  1690. if (thumbnailsEl) {
  1691. containerHeight = thumbnailsEl.clientHeight;
  1692. visibleThumbnails = Math.floor(containerHeight / thumbnailHeight);
  1693. updateThumbnailsPosition();
  1694. }
  1695. }, 100);
  1696. function getCurrentIndex() {
  1697. const current = document.querySelector('.thumbnail-item.permanently-active') || document.querySelector('.thumbnail-item.active');
  1698. if (! current) 
  1699. return 0;
  1700. const idxAttr = current.getAttribute('data-index');
  1701. return idxAttr ? parseInt(idxAttr, 10) : Math.max(0, thumbnailItems.indexOf(current));
  1702. }
  1703. function selectThumbnailByIndex(newIndex) {
  1704. if (! thumbnailItems.length) 
  1705. return;
  1706. const total = thumbnailItems.length;
  1707. if (newIndex < 0) 
  1708. newIndex = 0;
  1709. if (newIndex > total - 1) 
  1710. newIndex = total - 1;
  1711. // Reset classes
  1712. thumbnailItems.forEach(t => t.classList.remove('active', 'permanently-active'));
  1713. const target = thumbnailItems[newIndex];
  1714. if (! target) 
  1715. return;
  1716. target.classList.add('active', 'permanently-active');
  1717. changeMainImage(target, newIndex);
  1718. // Ajuster le carrousel pour voir la miniature sélectionnée
  1719. ensureThumbnailVisible(newIndex);
  1720. }
  1721. function ensureThumbnailVisible(index) {
  1722. if (! thumbnailsWrapper || ! thumbnailItems.length) 
  1723. return;
  1724. const targetTop = index * thumbnailHeight;
  1725. const visibleTop = currentThumbnailOffset * thumbnailHeight;
  1726. const visibleBottom = visibleTop + containerHeight;
  1727. // Si la miniature est au-dessus de la zone visible
  1728. if (targetTop < visibleTop) {
  1729. currentThumbnailOffset = index;
  1730. updateThumbnailsPosition();
  1731. }
  1732. // Si la miniature est en-dessous de la zone visible 
  1733. else if (targetTop + thumbnailHeight > visibleBottom) {
  1734. currentThumbnailOffset = Math.max(0, index - visibleThumbnails + 1);
  1735. updateThumbnailsPosition();
  1736. }
  1737. }
  1738. function updateThumbnailsPosition() {
  1739. if (!thumbnailsWrapper) 
  1740. return;
  1741. const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
  1742. currentThumbnailOffset = Math.max(0, Math.min(currentThumbnailOffset, maxOffset));
  1743. thumbnailsWrapper.style.transform = `translateY(-${
  1744. currentThumbnailOffset * thumbnailHeight
  1745. }px)`;
  1746. updateNavButtons();
  1747. }
  1748. function updateNavButtons() {
  1749. if (!thumbnailItems.length) 
  1750. return;
  1751. const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
  1752. const canScrollUp = currentThumbnailOffset > 0;
  1753. const canScrollDown = currentThumbnailOffset < maxOffset;
  1754. if (navUpBtn) {
  1755. navUpBtn.classList.toggle('disabled', ! canScrollUp);
  1756. }
  1757. if (navDownBtn) {
  1758. navDownBtn.classList.toggle('disabled', ! canScrollDown);
  1759. }
  1760. }
  1761. // Navigation avec les boutons
  1762. if(navUpBtn) {
  1763. navUpBtn.addEventListener('click', function (e) {
  1764. e.preventDefault();
  1765. if (currentThumbnailOffset > 0) {
  1766. currentThumbnailOffset --;
  1767. updateThumbnailsPosition();
  1768. }
  1769. });
  1770. }
  1771. if(navDownBtn) {
  1772. navDownBtn.addEventListener('click', function (e) {
  1773. e.preventDefault();
  1774. const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
  1775. if (currentThumbnailOffset < maxOffset) {
  1776. currentThumbnailOffset ++;
  1777. updateThumbnailsPosition();
  1778. }
  1779. });
  1780. }
  1781. // Initialiser le carrousel
  1782. if(thumbnailsWrapper && thumbnailItems.length > 0) {
  1783. updateThumbnailsPosition();
  1784. // S'assurer que la première miniature est visible
  1785. ensureThumbnailVisible(0);
  1786. }
  1787. // Navigation sur l'image principale
  1788. const mainPrev = document.getElementById('main-image-prev');
  1789. const mainNext = document.getElementById('main-image-next');
  1790. if(mainPrev) {
  1791. mainPrev.addEventListener('click', function (e) {
  1792. e.preventDefault();
  1793. const idx = getCurrentIndex();
  1794. selectThumbnailByIndex(idx - 1);
  1795. });
  1796. }
  1797. if(mainNext) {
  1798. mainNext.addEventListener('click', function (e) {
  1799. e.preventDefault();
  1800. const idx = getCurrentIndex();
  1801. selectThumbnailByIndex(idx + 1);
  1802. });
  1803. }
  1804. }
  1805. );
  1806. // --- Prix de gros : calcul du prix unitaire dynamique ---
  1807. (function () {
  1808. const bulk = document.getElementById('bulk-pricing');
  1809. const qtyInputEl = document.getElementById('sst');
  1810. const unitPriceSpan = document.getElementById('unit-price-value');
  1811. const mainUnitPriceSpan = document.getElementById('main-unit-price');
  1812. const totalPriceSpan = document.getElementById('total-price-value');
  1813. const savingsAmountSpan = document.getElementById('savings-amount');
  1814. const savingsPercentSpan = document.getElementById('savings-percent');
  1815. const unitPriceInput = document.getElementById('unit-price-input');
  1816. const tierRowsTbody = document.getElementById('bulk-tier-rows');
  1817. if (! bulk || ! qtyInputEl || ! unitPriceSpan || ! mainUnitPriceSpan || ! unitPriceInput) 
  1818. return;
  1819. let tiers = [];
  1820. try {
  1821. tiers = JSON.parse(bulk.getAttribute('data-tiers') || '[]');
  1822. } catch (e) {
  1823. tiers = [];
  1824. }
  1825. if (!Array.isArray(tiers) || tiers.length === 0) 
  1826. return;
  1827. tiers.sort(function (a, b) {
  1828. return(a.min || 0) - (b.min || 0);
  1829. });
  1830. const basePrice = (function () {
  1831. const one = tiers.find(t => (t.min || 0) <= 1);
  1832. return one ? parseFloat(one.price) : parseFloat(tiers[0].price);
  1833. })();
  1834. function getUnitPriceForQty(qty) {
  1835. let candidate = tiers[0] || null;
  1836. for (let i = 0; i < tiers.length; i++) {
  1837. if (qty >= (tiers[i].min || 1)) {
  1838. candidate = tiers[i];
  1839. }
  1840. }
  1841. return candidate ? parseFloat(candidate.price) : (tiers[0] ? parseFloat(tiers[0].price) : basePrice);
  1842. }
  1843. function formatPrice(val) {
  1844. return(parseFloat(val) || 0).toFixed(2);
  1845. }
  1846. function highlightActiveTier(qty) {
  1847. if (! tierRowsTbody) 
  1848. return;
  1849. const rows = tierRowsTbody.querySelectorAll('tr');
  1850. rows.forEach(r => r.classList.remove('table-warning'));
  1851. let activeRow = null;
  1852. rows.forEach(r => {
  1853. const m = parseInt(r.getAttribute('data-min'), 10) || 1;
  1854. if (qty >= m) 
  1855. activeRow = r;
  1856. });
  1857. if (activeRow) 
  1858. activeRow.classList.add('table-warning');
  1859. }
  1860. function refreshPrices() {
  1861. const qty = Math.max(1, parseInt(qtyInputEl.value, 10) || 1);
  1862. const unit = getUnitPriceForQty(qty);
  1863. const total = unit * qty;
  1864. const saveAmount = Math.max(0, (basePrice - unit) * qty);
  1865. const savePercent = Math.max(0, 100 * (basePrice - unit) / basePrice);
  1866. unitPriceSpan.textContent = formatPrice(unit) + ' HTG';
  1867. mainUnitPriceSpan.textContent = formatPrice(unit);
  1868. if (totalPriceSpan) 
  1869. totalPriceSpan.textContent = formatPrice(total);
  1870. if (savingsAmountSpan) 
  1871. savingsAmountSpan.textContent = formatPrice(saveAmount);
  1872. if (savingsPercentSpan) 
  1873. savingsPercentSpan.textContent = Math.round(savePercent);
  1874. unitPriceInput.value = formatPrice(unit);
  1875. highlightActiveTier(qty);
  1876. }
  1877. qtyInputEl.addEventListener('input', refreshPrices);
  1878. qtyInputEl.addEventListener('change', refreshPrices);
  1879. setTimeout(refreshPrices, 50);
  1880. setInterval(refreshPrices, 350);
  1881. })();
  1882. // --- /Prix de gros ---
  1883. // Injection du prix unitaire dans l'ajout au panier
  1884. (function () {
  1885. const addToCartBtn = document.getElementById('add-to-cart-btn');
  1886. if (! addToCartBtn) 
  1887. return;
  1888. addToCartBtn.addEventListener('click', function () {
  1889. const unitPriceInput = document.getElementById('unit-price-input');
  1890. if (unitPriceInput) {
  1891. addToCartBtn.setAttribute('data-unit-price', unitPriceInput.value);
  1892. }
  1893. });
  1894. })();
  1895. // Fonction pour ouvrir le modal de zoom avec loader
  1896. function openImageZoom () {
  1897. const mainImage = document.getElementById('main-product-image');
  1898. const zoomModal = document.getElementById('image-zoom-modal');
  1899. const zoomImage = document.getElementById('zoom-modal-image');
  1900. const zoomLoader = document.getElementById('zoom-loader');
  1901. if (mainImage && zoomModal && zoomImage) { // Afficher le loader
  1902. if (zoomLoader) {
  1903. zoomLoader.style.display = 'flex';
  1904. }
  1905. // Masquer l'image et réinitialiser
  1906. zoomImage.classList.remove('loaded');
  1907. zoomImage.style.opacity = '0';
  1908. // Ouvrir le modal avec animation
  1909. zoomModal.classList.add('active');
  1910. document.body.style.overflow = 'hidden';
  1911. // Charger l'image
  1912. const imageUrl = mainImage.src;
  1913. const tempImage = new Image();
  1914. tempImage.onload = function () {
  1915. zoomImage.src = imageUrl;
  1916. // Masquer le loader et afficher l'image avec transition
  1917. setTimeout(function () {
  1918. if (zoomLoader) {
  1919. zoomLoader.style.display = 'none';
  1920. }
  1921. zoomImage.classList.add('loaded');
  1922. zoomImage.style.opacity = '1';
  1923. }, 200);
  1924. };
  1925. tempImage.onerror = function () {
  1926. if (zoomLoader) {
  1927. zoomLoader.style.display = 'none';
  1928. }
  1929. zoomImage.src = imageUrl;
  1930. zoomImage.classList.add('loaded');
  1931. };
  1932. tempImage.src = imageUrl;
  1933. }
  1934. }
  1935. // Fonction pour fermer le modal de zoom avec animation
  1936. function closeImageZoom () {
  1937. const zoomModal = document.getElementById('image-zoom-modal');
  1938. const zoomImage = document.getElementById('zoom-modal-image');
  1939. const zoomLoader = document.getElementById('zoom-loader');
  1940. if (zoomModal) { // Animation de fermeture
  1941. if (zoomImage) {
  1942. zoomImage.style.opacity = '0';
  1943. zoomImage.classList.remove('loaded');
  1944. }
  1945. setTimeout(function () {
  1946. zoomModal.classList.remove('active');
  1947. document.body.style.overflow = 'auto';
  1948. // Réinitialiser le loader pour la prochaine ouverture
  1949. if (zoomLoader) {
  1950. zoomLoader.style.display = 'none';
  1951. }
  1952. }, 200);
  1953. }
  1954. }
  1955. // Fermer le modal en cliquant en dehors
  1956. document.addEventListener('DOMContentLoaded', function () {
  1957. const zoomModal = document.getElementById('image-zoom-modal');
  1958. if (zoomModal) {
  1959. zoomModal.addEventListener('click', function (e) {
  1960. if (e.target === zoomModal) {
  1961. closeImageZoom();
  1962. }
  1963. });
  1964. }
  1965. });
  1966.     </script>
  1967.     <!-- Modal de zoom d'image -->
  1968.     <div id="image-zoom-modal" class="image-zoom-modal">
  1969.         <span class="close-zoom" onclick="closeImageZoom()" title="Fermer">&times;</span>
  1970.         <div
  1971.             class="zoom-modal-content">
  1972.             <!-- Loader -->
  1973.             <div class="zoom-loader" id="zoom-loader" style="display: none;">
  1974.                 <div class="zoom-loader-spinner"></div>
  1975.                 <div class="zoom-loader-text">Chargement...</div>
  1976.             </div>
  1977.             <!-- Image -->
  1978.             <img id="zoom-modal-image" src="" alt="{{ product.name }}">
  1979.         </div>
  1980.     </div>
  1981.     {% if dropshipReferral %}
  1982.         <!-- Modal d'affiliation -->
  1983.         <div id="dropship-modal" class="dropship-referral-modal" style="display: none;">
  1984.             <div class="dropship-modal-overlay"></div>
  1985.             <div class="dropship-modal-content">
  1986.                 <button type="button" class="dropship-modal-close" onclick="closeDropshipModal()" aria-label="Fermer">
  1987.                     &times;
  1988.                 </button>
  1989.                 <div class="dropship-modal-header">
  1990.                     <div class="dropship-icon">
  1991.                         <i class="fas fa-gift"></i>
  1992.                     </div>
  1993.                     <h3>Produit recommandé par
  1994.                         {{ dropshipReferral.affiliateName }}</h3>
  1995.                     <p class="dropship-subtitle">Vous avez été dirigé vers ce produit par un de nos partenaires</p>
  1996.                 </div>
  1997.                 <div class="dropship-modal-body">
  1998.                     <div class="dropship-product-info">
  1999.                         {% if product.images|length > 0 %}
  2000.                             <img src="{{ asset(product.images[0]) }}" alt="{{ product.name }}" class="dropship-product-image" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
  2001.                             <div class="dropship-product-image-placeholder" style="display: none; width: 100px; height: 100px; background: #f0f0f0; border-radius: 8px; align-items: center; justify-content: center; color: #999;">
  2002.                                 <i class="fas fa-image" style="font-size: 32px;"></i>
  2003.                             </div>
  2004.                         {% else %}
  2005.                             <div class="dropship-product-image-placeholder" style="width: 100px; height: 100px; background: #f0f0f0; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #999;">
  2006.                                 <i class="fas fa-image" style="font-size: 32px;"></i>
  2007.                             </div>
  2008.                         {% endif %}
  2009.                         <div class="dropship-product-details">
  2010.                             <h4>{{ product.name }}</h4>
  2011.                             <div class="dropship-product-price">
  2012.                                 <span class="price">{{ product.price|number_format(2, ',', ' ') }}
  2013.                                     HTG</span>
  2014.                             </div>
  2015.                         </div>
  2016.                     </div>
  2017.                     <div class="dropship-message">
  2018.                         <p>
  2019.                             <strong>{{ dropshipReferral.affiliateName }}</strong>
  2020.                             vous recommande ce produit. Souhaitez-vous l'ajouter à votre panier ?</p>
  2021.                     </div>
  2022.                 </div>
  2023.                 <div class="dropship-modal-footer">
  2024.                     <button type="button" class="btn btn-secondary" onclick="closeDropshipModal()">
  2025.                         Parcourir d'abord
  2026.                     </button>
  2027.                     <button type="button" class="btn btn-primary dropship-add-to-cart" onclick="addToCartFromDropship()">
  2028.                         <i class="fas fa-shopping-cart"></i>
  2029.                         Ajouter au panier
  2030.                     </button>
  2031.                 </div>
  2032.             </div>
  2033.         </div>
  2034.         <style>
  2035.             .dropship-referral-modal {
  2036.                 position: fixed;
  2037.                 top: 0;
  2038.                 left: 0;
  2039.                 width: 100%;
  2040.                 height: 100%;
  2041.                 z-index: 10000;
  2042.                 display: flex;
  2043.                 align-items: center;
  2044.                 justify-content: center;
  2045.             }
  2046.             .dropship-modal-overlay {
  2047.                 position: absolute;
  2048.                 top: 0;
  2049.                 left: 0;
  2050.                 width: 100%;
  2051.                 height: 100%;
  2052.                 background: rgba(0, 0, 0, 0.6);
  2053.                 backdrop-filter: blur(4px);
  2054.             }
  2055.             .dropship-modal-content {
  2056.                 position: relative;
  2057.                 background: white;
  2058.                 border-radius: 16px;
  2059.                 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  2060.                 max-width: 500px;
  2061.                 width: 90%;
  2062.                 max-height: 90vh;
  2063.                 overflow-y: auto;
  2064.                 z-index: 10001;
  2065.                 animation: dropshipModalSlideIn 0.3s ease-out;
  2066.             }
  2067.             @keyframes dropshipModalSlideIn {
  2068.                 from {
  2069.                     opacity: 0;
  2070.                     transform: translateY(-30px) scale(0.95);
  2071.                 }
  2072.                 to {
  2073.                     opacity: 1;
  2074.                     transform: translateY(0) scale(1);
  2075.                 }
  2076.             }
  2077.             .dropship-modal-close {
  2078.                 position: absolute;
  2079.                 top: 15px;
  2080.                 right: 15px;
  2081.                 background: none;
  2082.                 border: none;
  2083.                 font-size: 28px;
  2084.                 color: #666;
  2085.                 cursor: pointer;
  2086.                 width: 35px;
  2087.                 height: 35px;
  2088.                 display: flex;
  2089.                 align-items: center;
  2090.                 justify-content: center;
  2091.                 border-radius: 50%;
  2092.                 transition: all 0.2s;
  2093.                 z-index: 10;
  2094.             }
  2095.             .dropship-modal-close:hover {
  2096.                 background: #f0f0f0;
  2097.                 color: #333;
  2098.             }
  2099.             .dropship-modal-header {
  2100.                 padding: 30px 30px 20px;
  2101.                 text-align: center;
  2102.                 border-bottom: 1px solid #f0f0f0;
  2103.             }
  2104.             .dropship-icon {
  2105.                 width: 70px;
  2106.                 height: 70px;
  2107.                 margin: 0 auto 15px;
  2108.                 background: linear-gradient(135deg, #ffa200 0%, #ff8c00 100%);
  2109.                 border-radius: 50%;
  2110.                 display: flex;
  2111.                 align-items: center;
  2112.                 justify-content: center;
  2113.                 font-size: 32px;
  2114.                 color: white;
  2115.                 box-shadow: 0 4px 15px rgba(255, 162, 0, 0.3);
  2116.             }
  2117.             .dropship-modal-header h3 {
  2118.                 margin: 0 0 8px;
  2119.                 font-size: 22px;
  2120.                 font-weight: 600;
  2121.                 color: #333;
  2122.             }
  2123.             .dropship-subtitle {
  2124.                 margin: 0;
  2125.                 color: #666;
  2126.                 font-size: 14px;
  2127.             }
  2128.             .dropship-modal-body {
  2129.                 padding: 25px 30px;
  2130.             }
  2131.             .dropship-product-info {
  2132.                 display: flex;
  2133.                 gap: 15px;
  2134.                 margin-bottom: 20px;
  2135.                 padding: 15px;
  2136.                 background: #f8f9fa;
  2137.                 border-radius: 10px;
  2138.             }
  2139.             .dropship-product-image {
  2140.                 width: 80px;
  2141.                 height: 80px;
  2142.                 object-fit: cover;
  2143.                 border-radius: 8px;
  2144.                 flex-shrink: 0;
  2145.             }
  2146.             .dropship-product-details {
  2147.                 flex: 1;
  2148.             }
  2149.             .dropship-product-details h4 {
  2150.                 margin: 0 0 8px;
  2151.                 font-size: 16px;
  2152.                 font-weight: 600;
  2153.                 color: #333;
  2154.             }
  2155.             .dropship-product-price {
  2156.                 margin-top: 5px;
  2157.             }
  2158.             .dropship-product-price .price {
  2159.                 font-size: 20px;
  2160.                 font-weight: 700;
  2161.                 color: #ffa200;
  2162.             }
  2163.             .dropship-message {
  2164.                 padding: 15px;
  2165.                 background: #fff9e6;
  2166.                 border-left: 4px solid #ffa200;
  2167.                 border-radius: 6px;
  2168.             }
  2169.             .dropship-message p {
  2170.                 margin: 0;
  2171.                 color: #666;
  2172.                 line-height: 1.6;
  2173.             }
  2174.             .dropship-modal-footer {
  2175.                 padding: 20px 30px 30px;
  2176.                 display: flex;
  2177.                 gap: 12px;
  2178.                 justify-content: flex-end;
  2179.                 border-top: 1px solid #f0f0f0;
  2180.             }
  2181.             .dropship-modal-footer .btn {
  2182.                 padding: 12px 24px;
  2183.                 border-radius: 8px;
  2184.                 font-weight: 500;
  2185.                 border: none;
  2186.                 cursor: pointer;
  2187.                 transition: all 0.2s;
  2188.                 font-size: 15px;
  2189.             }
  2190.             .dropship-modal-footer .btn-secondary {
  2191.                 background: #f0f0f0;
  2192.                 color: #666;
  2193.             }
  2194.             .dropship-modal-footer .btn-secondary:hover {
  2195.                 background: #e0e0e0;
  2196.             }
  2197.             .dropship-modal-footer .btn-primary {
  2198.                 background: #ffa200;
  2199.                 color: white;
  2200.             }
  2201.             .dropship-modal-footer .btn-primary:hover {
  2202.                 background: #e8910a;
  2203.                 transform: translateY(-2px);
  2204.                 box-shadow: 0 4px 12px rgba(255, 162, 0, 0.3);
  2205.             }
  2206.             .dropship-modal-footer .btn-primary:disabled {
  2207.                 opacity: 0.6;
  2208.                 cursor: not-allowed;
  2209.                 transform: none;
  2210.             }
  2211.         </style>
  2212.         <script>
  2213.             // Afficher le modal automatiquement au chargement de la page
  2214. document.addEventListener('DOMContentLoaded', function () {
  2215. const dropshipModal = document.getElementById('dropship-modal');
  2216. if (dropshipModal) {
  2217. setTimeout(function () {
  2218. dropshipModal.style.display = 'flex';
  2219. document.body.style.overflow = 'hidden';
  2220. }, 500); // Délai de 500ms pour une meilleure UX
  2221. }
  2222. });
  2223. function closeDropshipModal () {
  2224. const dropshipModal = document.getElementById('dropship-modal');
  2225. if (dropshipModal) {
  2226. dropshipModal.style.display = 'none';
  2227. document.body.style.overflow = 'auto';
  2228. }
  2229. }
  2230. function addToCartFromDropship () {
  2231. const addBtn = document.querySelector('.dropship-add-to-cart');
  2232. if (! addBtn) 
  2233. return;
  2234. // Désactiver le bouton pendant le traitement
  2235. addBtn.disabled = true;
  2236. addBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Ajout en cours...';
  2237. const productId = {{ product.id }};
  2238. const qty = parseInt(document.getElementById('sst') ?. value || '1');
  2239. // Utiliser fetch pour ajouter au panier
  2240. fetch('{{ path("cart_add") }}', {
  2241. method: 'POST',
  2242. headers: {
  2243. 'Content-Type': 'application/x-www-form-urlencoded',
  2244. 'X-Requested-With': 'XMLHttpRequest'
  2245. },
  2246. body: new URLSearchParams(
  2247. {productId: productId, qty: qty}
  2248. )
  2249. }).then(response => response.json()).then(data => {
  2250. if (data.ok) { // Afficher un message de succès
  2251. const modalBody = document.querySelector('.dropship-modal-body');
  2252. if (modalBody) {
  2253. modalBody.innerHTML = `
  2254.                                 <div style="text-align: center; padding: 30px 20px;">
  2255.                                     <div style="width: 80px; height: 80px; margin: 0 auto 20px; background: #d4edda; border-radius: 50%; display: flex; align-items: center; justify-content: center;">
  2256.                                         <i class="fas fa-check" style="font-size: 40px; color: #28a745;"></i>
  2257.                                     </div>
  2258.                                     <h3 style="color: #28a745; margin-bottom: 10px;">Produit ajouté avec succès !</h3>
  2259.                                     <p style="color: #666; margin-bottom: 20px;">Merci d'avoir utilisé le lien de recommandation de <strong>{{ dropshipReferral.affiliateName }}</strong></p>
  2260.                                     <p style="color: #666; font-size: 14px;">Vous pouvez continuer vos achats ou aller directement au panier.</p>
  2261.                                 </div>
  2262.                             `;
  2263. }
  2264. const modalFooter = document.querySelector('.dropship-modal-footer');
  2265. if (modalFooter) {
  2266. modalFooter.innerHTML = `
  2267.                                 <button type="button" class="btn btn-secondary" onclick="closeDropshipModal()">
  2268.                                     Continuer les achats
  2269.                                 </button>
  2270.                                 <a href="{{ path('cart') }}" class="btn btn-primary" style="text-decoration: none; display: inline-block;">
  2271.                                     <i class="fas fa-shopping-cart"></i> Voir le panier
  2272.                                 </a>
  2273.                             `;
  2274. }
  2275. // Mettre à jour le compteur du panier si présent
  2276. const cartCount = document.querySelector('.cart-count, .cart_count, [data-cart-count]');
  2277. if (cartCount) {
  2278. cartCount.textContent = data.totalQty || 0;
  2279. }
  2280. } else {
  2281. showCustomAlert('Erreur lors de l\'ajout au panier. Veuillez réessayer.', 'error');
  2282. addBtn.disabled = false;
  2283. addBtn.innerHTML = '<i class="fas fa-shopping-cart"></i> Ajouter au panier';
  2284. }
  2285. }).catch(error => {
  2286. console.error('Erreur:', error);
  2287. showCustomAlert('Une erreur est survenue. Veuillez réessayer.', 'error');
  2288. addBtn.disabled = false;
  2289. addBtn.innerHTML = '<i class="fas fa-shopping-cart"></i> Ajouter au panier';
  2290. });
  2291. }
  2292. // Fermer le modal en cliquant sur l'overlay
  2293. document.addEventListener('DOMContentLoaded', function () {
  2294. const dropshipModal = document.getElementById('dropship-modal');
  2295. if (dropshipModal) {
  2296. const overlay = dropshipModal.querySelector('.dropship-modal-overlay');
  2297. if (overlay) {
  2298. overlay.addEventListener('click', function () {
  2299. closeDropshipModal();
  2300. });
  2301. }
  2302. }
  2303. });
  2304.         </script>
  2305.     {% endif %}
  2306.     <!-- Le Modal personnalisé et showCustomAlert ont été déplacés dans base_home.html.twig pour une portée globale -->
  2307.     <script>
  2308.         // Remplacer window.alert par showCustomAlert
  2309.         window.alert = window.showCustomAlert || function(message) { console.log("Alert:", message); };
  2310.     </script>
  2311. {% endblock %}