<!DOCTYPE html>
<html lang="fr" class="no-js" data-theme="light" data-bs-theme="light">
<head>
<!-- Mobile Specific Meta -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="MaketOu">
<meta name="theme-color" content="#ffa200">
<meta name="msapplication-TileColor" content="#ffa200">
<meta name="msapplication-config" content="{{ asset('favicon/browserconfig.xml') }}">
<!-- Favicon-->
<link rel="shortcut icon" href="{{ asset('favicon/favicon-16x16.png') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('favicon/apple-touch-icon.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon/favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon/favicon-16x16.png') }}">
<!-- PWA Manifest -->
<link rel="manifest" href="{{ asset('manifest.json') }}">
<!-- Author Meta -->
<meta name="author" content="Foulgor Tech">
<!-- Meta Description -->
<meta name="description" content="">
<!-- Meta Keyword -->
<meta name="keywords" content="">
<!-- meta character set -->
<meta charset="UTF-8">
<!-- Site Title -->
<title>{% block title %}MaketOu{% endblock %}</title>
<script>
(function () {
try {
var storageKey = 'maketou-theme';
var savedTheme = window.localStorage.getItem(storageKey);
var theme = savedTheme === 'dark' || savedTheme === 'light'
? savedTheme
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.setAttribute('data-bs-theme', theme);
document.documentElement.classList.toggle('dark-mode', theme === 'dark');
} catch (error) {
document.documentElement.setAttribute('data-theme', 'light');
document.documentElement.setAttribute('data-bs-theme', 'light');
}
})();
</script>
<!--
CSS
============================================= -->
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" />
<link rel="stylesheet" href="{{ asset('ui/css/linearicons.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/font-awesome.min.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/themify-icons.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/owl.carousel.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/nice-select.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/nouislider.min.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/ion.rangeSlider.css') }}"/>
<link rel="stylesheet" href="{{ asset('ui/css/ion.rangeSlider.skinFlat.css') }}"/>
<link rel="stylesheet" href="{{ asset('ui/css/magnific-popup.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/main.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/buttons-unified.css') }}">
<link rel="stylesheet" href="{{ asset('ui/css/theme-public.css') }}">
<meta charset="UTF-8">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
<style>
/* Styles pour l'icône de notification */
.notification-icon {
position: relative;
display: inline-block;
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.notification-icon:hover {
color: #007bff;
}
.notification-badge {
position: absolute;
top: -4px;
right: -2px;
background: #dc3545 !important;
color: white !important;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 8px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-weight: bold !important;
animation: pulse 2s infinite;
z-index: 10;
line-height: 1 !important;
}
/* Forcer le texte en blanc pour tous les éléments du badge - avec spécificité maximale */
.notification-badge,
.notification-badge.text-white,
.notification-badge *,
#notificationBadge,
#notificationBadge.text-white,
#notificationBadge *,
span.notification-badge,
span.notification-badge.text-white,
span#notificationBadge,
span#notificationBadge.text-white {
color: white !important;
text-color: white !important;
-webkit-text-fill-color: white !important;
}
/* S'assurer que le contenu texte est bien en blanc */
.notification-badge::before,
.notification-badge::after {
color: white !important;
}
/* Forcer la couleur pour tous les sélecteurs possibles avec spécificité maximale */
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon .notification-badge,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon .notification-badge.text-white,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon span.notification-badge,
.notification-icon .notification-badge,
.notification-icon .notification-badge.text-white,
.notification-icon span.notification-badge,
a.notification-icon .notification-badge,
a.notification-icon span.notification-badge {
color: white !important;
text-color: white !important;
-webkit-text-fill-color: white !important;
}
/* Forcer la couleur pour tous les enfants directs */
.notification-icon > .notification-badge,
.notification-icon > span.notification-badge {
color: white !important;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.notification-icon:hover .notification-badge {
animation: none;
}
/* Container pour les icônes - toujours alignées horizontalement */
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
justify-content: center !important;
gap: 15px !important;
list-style: none !important;
margin: 0 !important;
padding: 0 !important;
width: auto !important;
}
/* Surcharger les styles existants pour les li contenant le wrapper */
.header_area .navbar .nav.navbar-nav.navbar-right li:has(.navbar-icons-wrapper) {
margin-left: 0 !important;
margin-right: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Styles pour les icônes */
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .cart,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .search {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
min-height: 40px !important;
color: #333 !important;
text-decoration: none !important;
transition: all 0.3s ease !important;
border-radius: 50% !important;
position: relative !important;
margin: 0 !important;
padding: 0 !important;
}
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .cart:hover,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon:hover,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .search:hover {
background-color: #f0f0f0 !important;
color: #ffa200 !important;
transform: scale(1.1) !important;
}
/* Surcharger le line-height de main.css */
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .cart span,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .notification-icon span,
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .search span {
font-size: 18px !important;
line-height: 1 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: inherit !important;
font-weight: normal !important;
}
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper .search {
background: transparent !important;
border: 0 !important;
cursor: pointer !important;
padding: 0 !important;
}
/* Alignement des icônes navbar sur mobile */
@media (max-width: 991.98px) {
/* Container navbar-right en colonne sur mobile */
.header_area .navbar .nav.navbar-nav.navbar-right {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
width: 100% !important;
gap: 0 !important;
margin: 0 !important;
padding: 10px 0 !important;
}
/* Le container d'icônes reste horizontal */
.header_area .navbar .nav.navbar-nav.navbar-right .navbar-icons-wrapper {
width: 100% !important;
justify-content: center !important;
display: flex !important;
flex-direction: row !important;
}
/* Le bouton compte en bas - pleine largeur */
.header_area .navbar .nav.navbar-nav.navbar-right li:last-child {
width: 100% !important;
display: flex !important;
justify-content: center !important;
margin-top: 12px !important;
padding-top: 12px !important;
border-top: 1px solid #e0e0e0 !important;
}
/* Bouton compte responsive */
.header_area .navbar .nav.navbar-nav.navbar-right .btn-account {
width: 100%;
max-width: 200px;
padding: 10px 20px;
font-size: 14px;
}
}
/* Styles pour les messages flash - Design moderne */
.flash-message {
animation: slideInDownFlash 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: visible !important;
border: none !important;
padding: 16px 20px !important;
border-radius: 12px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
display: flex !important;
align-items: center;
gap: 12px;
margin-bottom: 15px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
/* S'assurer que les icônes Font Awesome sont visibles */
.flash-message i.fa {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
width: auto !important;
height: auto !important;
font-family: "FontAwesome" !important;
font-style: normal !important;
font-weight: normal !important;
text-rendering: auto !important;
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
line-height: 1 !important;
flex-shrink: 0 !important;
}
/* Forcer l'affichage des pseudo-éléments Font Awesome */
.flash-message i.fa::before {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
content: attr(data-icon) !important;
}
/* Styles spécifiques pour chaque icône */
.flash-message i.fa-check-circle::before {
content: "\f058" !important;
}
.flash-message i.fa-exclamation-circle::before {
content: "\f06a" !important;
}
.flash-message i.fa-exclamation-triangle::before {
content: "\f071" !important;
}
.flash-message i.fa-info-circle::before {
content: "\f05a" !important;
}
.flash-message:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2) !important;
transform: translateY(-2px);
}
.flash-message::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 5px;
background: linear-gradient(180deg, currentColor 0%, rgba(255,255,255,0.3) 100%);
border-radius: 12px 0 0 12px;
}
.flash-message.alert-success {
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%) !important;
color: #155724 !important;
border-left: 5px solid #28a745 !important;
}
.flash-message.alert-success i.fa {
color: #28a745 !important;
font-size: 1.3rem !important;
display: inline-block !important;
margin-right: 8px;
vertical-align: middle;
}
.flash-message.alert-danger {
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%) !important;
color: #721c24 !important;
border-left: 5px solid #dc3545 !important;
}
.flash-message.alert-danger i.fa {
color: #dc3545 !important;
font-size: 1.3rem !important;
display: inline-block !important;
margin-right: 8px;
vertical-align: middle;
}
.flash-message.alert-warning {
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%) !important;
color: #856404 !important;
border-left: 5px solid #ffc107 !important;
}
.flash-message.alert-warning i.fa {
color: #ffa200 !important;
font-size: 1.3rem !important;
display: inline-block !important;
margin-right: 8px;
vertical-align: middle;
}
.flash-message.alert-info {
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%) !important;
color: #0c5460 !important;
border-left: 5px solid #17a2b8 !important;
}
.flash-message.alert-info i.fa {
color: #17a2b8 !important;
font-size: 1.3rem !important;
display: inline-block !important;
margin-right: 8px;
vertical-align: middle;
}
.flash-message strong {
flex: 1;
font-weight: 600;
font-size: 0.95rem;
line-height: 1.5;
}
.flash-message .btn-close {
opacity: 0.7;
padding: 8px !important;
margin: -8px -8px -8px auto !important;
transition: all 0.2s ease;
border-radius: 50% !important;
background: rgba(0, 0, 0, 0.05) !important;
width: 32px !important;
height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
border: none !important;
cursor: pointer !important;
position: relative !important;
}
/* Ajouter une icône X visible dans le bouton */
.flash-message .btn-close::before {
content: '×' !important;
font-size: 24px !important;
line-height: 1 !important;
color: currentColor !important;
font-weight: bold !important;
display: block !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
.flash-message .btn-close:hover {
opacity: 1 !important;
background: rgba(0, 0, 0, 0.15) !important;
transform: rotate(90deg) !important;
}
.flash-message .btn-close:hover::before {
transform: translate(-50%, -50%) rotate(-90deg) !important;
}
@keyframes slideInDownFlash {
from {
opacity: 0;
transform: translateY(-30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
#flash-messages-container {
margin-top: 20px;
margin-bottom: 0;
}
@media (max-width: 768px) {
.flash-message {
padding: 14px 16px !important;
font-size: 0.9rem;
}
.flash-message i {
font-size: 1.2rem !important;
}
}
/* Styles pour l'icône de comparaison */
.comparison-icon {
position: relative;
display: inline-block;
color: #333;
text-decoration: none;
transition: color 0.3s ease;
}
.comparison-icon:hover {
color: #ffa200;
}
.comparison-badge {
position: absolute;
top: 17px;
left: 12px;
background: #ffa200;
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
animation: pulse 2s infinite;
}
.comparison-icon:hover .comparison-badge {
animation: none;
}
/* Styles pour les mega-menus - style comme la barre de recherche */
.mega-menu {
width: 100%;
max-width: 1200px;
left: 50%;
transform: translateX(-50%);
padding: 25px;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
border: none;
background: white;
display: none;
position: absolute;
top: 100%;
z-index: 9999 !important;
margin-top: 10px;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;
}
.mega-menu.active {
display: block;
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
.nav-item.dropdown {
position: relative !important;
}
@media (min-width: 768px) {
.nav-item.dropdown {
position: relative !important;
}
.mega-menu {
position: absolute !important;
left: 50% !important;
transform: translateX(-50%) !important;
width: auto !important;
min-width: 600px !important;
}
}
/* Correction supplémentaire pour l'affichage sur la page d'accueil */
body.home .nav-item.dropdown,
.nav-item.dropdown {
position: relative !important;
}
body.home .mega-menu,
.mega-menu {
position: absolute !important;
left: 50% !important;
transform: translateX(-50%) translateY(0) !important;
width: auto !important;
min-width: 600px !important;
max-width: 90% !important;
top: 100% !important;
margin-top: 10px !important;
}
/* Assurer que les mega-menus sont toujours visibles au survol */
.nav-item.dropdown:hover .mega-menu {
opacity: 1 !important;
visibility: visible !important;
display: block !important;
transform: translateX(-50%) translateY(0) !important;
}
.mega-menu .dropdown-item {
padding: 0;
border: none;
}
.mega-menu .dropdown-header {
font-weight: 700;
color: #2c3e50;
margin-bottom: 18px;
padding-bottom: 10px;
border-bottom: 3px solid #3498db;
font-size: 16px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.mega-menu .dropdown-item a {
display: block;
padding: 10px 15px;
color: #555;
text-decoration: none;
border-radius: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.mega-menu .dropdown-item a::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(52, 152, 219, 0.1), transparent);
transition: left 0.5s;
}
.mega-menu .dropdown-item a:hover::before {
left: 100%;
}
.mega-menu .dropdown-item a:hover {
background: linear-gradient(135deg, #f8f9fa, #e3f2fd);
color: #2980b9;
transform: translateX(8px);
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2);
}
.mega-menu .dropdown-item a i {
width: 22px;
text-align: center;
margin-right: 8px;
color: #3498db;
transition: color 0.3s ease;
}
.mega-menu .dropdown-item a:hover i {
color: #2980b9;
}
/* Animation d'apparition améliorée */
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.mega-menu {
animation: slideInDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Effet de survol sur le lien principal */
.nav-item.dropdown .nav-link {
position: relative;
transition: all 0.3s ease;
}
.nav-item.dropdown .nav-link::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0;
height: 2px;
background: linear-gradient(90deg, #3498db, #2980b9);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-item.dropdown:hover .nav-link::after {
width: 100%;
}
.nav-item.dropdown:hover .nav-link {
color: #2980b9;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive */
@media (max-width: 768px) {
.mega-menu {
width: 100%;
left: 0;
padding: 15px;
}
.mega-menu .row {
margin: 0;
}
.mega-menu .col-md-3 {
margin-bottom: 20px;
}
}
</style>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
<!-- Script pour forcer l'application des styles CSS après chargement -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Forcer le masquage du toggle sur grands écrans
if (window.innerWidth >= 992) {
const togglers = document.querySelectorAll('.navbar-toggler, .header_area .navbar .navbar-toggler');
togglers.forEach(function(toggler) {
if (toggler) {
toggler.style.display = 'none';
toggler.style.visibility = 'hidden';
toggler.style.opacity = '0';
toggler.style.width = '0';
toggler.style.height = '0';
toggler.style.padding = '0';
toggler.style.margin = '0';
toggler.style.border = 'none';
toggler.style.position = 'absolute';
toggler.style.left = '-9999px';
}
});
const iconBars = document.querySelectorAll('.navbar-toggler .icon-bar, .header_area .navbar .navbar-toggler .icon-bar, .navbar-toggler-icon');
iconBars.forEach(function(bar) {
if (bar) {
bar.style.display = 'none';
bar.style.visibility = 'hidden';
}
});
}
// Alignement des icônes navbar sur mobile
const navbarRight = document.querySelectorAll('.navbar-right, .header_area .navbar .nav.navbar-nav.navbar-right');
navbarRight.forEach(function(nav) {
if (nav && window.innerWidth <= 991.98) {
// Mobile: colonne avec icônes alignées horizontalement
nav.style.display = 'flex';
nav.style.flexDirection = 'column';
nav.style.alignItems = 'center';
nav.style.textAlign = 'center';
// Le container d'icônes reste horizontal
const iconsContainer = nav.querySelector('.navbar-icons-wrapper');
if (iconsContainer) {
iconsContainer.style.display = 'flex';
iconsContainer.style.flexDirection = 'row';
iconsContainer.style.justifyContent = 'center';
}
// Le bouton compte en bas
const accountBtn = nav.querySelector('li:last-child');
if (accountBtn) {
accountBtn.style.width = '100%';
accountBtn.style.display = 'flex';
accountBtn.style.justifyContent = 'center';
accountBtn.style.marginTop = '12px';
accountBtn.style.paddingTop = '12px';
accountBtn.style.borderTop = '1px solid #e0e0e0';
}
} else if (nav && window.innerWidth > 991.98) {
// Desktop: alignement horizontal normal
nav.style.display = 'flex';
nav.style.flexDirection = 'row';
nav.style.alignItems = 'center';
nav.style.justifyContent = 'flex-end';
}
});
});
// Réappliquer lors du redimensionnement
window.addEventListener('resize', function() {
if (window.innerWidth >= 992) {
const togglers = document.querySelectorAll('.navbar-toggler, .header_area .navbar .navbar-toggler');
togglers.forEach(function(toggler) {
if (toggler) {
toggler.style.display = 'none';
toggler.style.visibility = 'hidden';
}
});
}
// Réappliquer l'alignement des icônes navbar
const navbarRight = document.querySelectorAll('.navbar-right, .header_area .navbar .nav.navbar-nav.navbar-right');
navbarRight.forEach(function(nav) {
if (nav && window.innerWidth <= 991.98) {
// Mobile: colonne avec icônes alignées horizontalement
nav.style.display = 'flex';
nav.style.flexDirection = 'column';
nav.style.alignItems = 'center';
nav.style.textAlign = 'center';
// Le container d'icônes reste horizontal
const iconsContainer = nav.querySelector('.navbar-icons-wrapper');
if (iconsContainer) {
iconsContainer.style.display = 'flex';
iconsContainer.style.flexDirection = 'row';
iconsContainer.style.justifyContent = 'center';
}
const accountBtn = nav.querySelector('li:last-child');
if (accountBtn) {
accountBtn.style.width = '100%';
accountBtn.style.display = 'flex';
accountBtn.style.justifyContent = 'center';
accountBtn.style.marginTop = '12px';
accountBtn.style.paddingTop = '12px';
accountBtn.style.borderTop = '1px solid #e0e0e0';
}
} else if (nav && window.innerWidth > 991.98) {
// Desktop: alignement horizontal normal
nav.style.display = 'flex';
nav.style.flexDirection = 'row';
nav.style.alignItems = 'center';
nav.style.justifyContent = 'flex-end';
}
});
});
</script>
{% set current_route = app.request.attributes.get('_route') %}
{% set is_account_route = current_route starts with 'ui_account_' or current_route == 'notifications_list' %}
<style>
/* Shared dark-mode fixes for the public account area. */
@media (prefers-color-scheme: dark) {
body.account-theme-scope {
background-color: #111827;
color: #e5e7eb;
}
body.account-theme-scope .card,
body.account-theme-scope .notification-item,
body.account-theme-scope .list-group-item,
body.account-theme-scope .modal-content,
body.account-theme-scope .dropdown-menu,
body.account-theme-scope .form-control,
body.account-theme-scope .form-select,
body.account-theme-scope .input-group-text,
body.account-theme-scope .accordion-item,
body.account-theme-scope .table,
body.account-theme-scope .table td,
body.account-theme-scope .table th {
background-color: #1f2937 !important;
border-color: #374151 !important;
color: #e5e7eb !important;
}
body.account-theme-scope .card-header,
body.account-theme-scope .card-header.bg-white,
body.account-theme-scope .bg-white,
body.account-theme-scope .account-menu-list .menu-main {
background-color: #182131 !important;
border-color: #374151 !important;
}
body.account-theme-scope .text-muted,
body.account-theme-scope .form-text,
body.account-theme-scope .form-label,
body.account-theme-scope small,
body.account-theme-scope .small,
body.account-theme-scope .btn-outline-secondary,
body.account-theme-scope .btn-outline-secondary i {
color: #aeb8c7 !important;
}
body.account-theme-scope h1,
body.account-theme-scope h2,
body.account-theme-scope h3,
body.account-theme-scope h4,
body.account-theme-scope h5,
body.account-theme-scope h6,
body.account-theme-scope .list-group-item,
body.account-theme-scope .card-title,
body.account-theme-scope .card-text,
body.account-theme-scope .notification-title,
body.account-theme-scope .notification-message,
body.account-theme-scope .notification-meta,
body.account-theme-scope .table,
body.account-theme-scope .table td,
body.account-theme-scope .table th,
body.account-theme-scope .table strong,
body.account-theme-scope .table a:not(.btn),
body.account-theme-scope .breadcrumb-banner,
body.account-theme-scope .breadcrumb-banner a,
body.account-theme-scope .breadcrumb-banner h1,
body.account-theme-scope .empty-state-title,
body.account-theme-scope .empty-state-text {
color: #e5e7eb !important;
}
body.account-theme-scope .account-menu-list .list-group-item:hover:not(.menu-action-active):not(.menu-main) {
background-color: #243041 !important;
}
body.account-theme-scope .notification-item.unread {
background-color: #172554 !important;
border-left-color: #60a5fa !important;
}
body.account-theme-scope .notification-item.archived {
opacity: 0.85;
}
body.account-theme-scope .btn-outline-primary,
body.account-theme-scope .btn-outline-secondary,
body.account-theme-scope .btn-outline-warning,
body.account-theme-scope .btn-outline-danger {
background-color: transparent !important;
}
body.account-theme-scope .empty-state-icon {
color: #6b7280 !important;
}
body.account-theme-scope .profile-picture-container .profile-picture,
body.account-theme-scope .profile-picture-mobile,
body.account-theme-scope .profile-picture-edit-icon,
body.account-theme-scope .profile-picture-edit-icon-mobile {
border-color: #111827 !important;
}
}
html.dark-mode body.account-theme-scope,
body.dark-mode.account-theme-scope,
html[data-theme="dark"] body.account-theme-scope,
body[data-theme="dark"].account-theme-scope,
html[data-bs-theme="dark"] body.account-theme-scope,
body[data-bs-theme="dark"].account-theme-scope {
background-color: #111827;
color: #e5e7eb;
}
html.dark-mode body.account-theme-scope .card,
html.dark-mode body.account-theme-scope .notification-item,
html.dark-mode body.account-theme-scope .list-group-item,
html.dark-mode body.account-theme-scope .modal-content,
html.dark-mode body.account-theme-scope .dropdown-menu,
html.dark-mode body.account-theme-scope .form-control,
html.dark-mode body.account-theme-scope .form-select,
html.dark-mode body.account-theme-scope .input-group-text,
html.dark-mode body.account-theme-scope .accordion-item,
html.dark-mode body.account-theme-scope .table,
html.dark-mode body.account-theme-scope .table td,
html.dark-mode body.account-theme-scope .table th,
body.dark-mode.account-theme-scope .card,
body.dark-mode.account-theme-scope .notification-item,
body.dark-mode.account-theme-scope .list-group-item,
body.dark-mode.account-theme-scope .modal-content,
body.dark-mode.account-theme-scope .dropdown-menu,
body.dark-mode.account-theme-scope .form-control,
body.dark-mode.account-theme-scope .form-select,
body.dark-mode.account-theme-scope .input-group-text,
body.dark-mode.account-theme-scope .accordion-item,
body.dark-mode.account-theme-scope .table,
body.dark-mode.account-theme-scope .table td,
body.dark-mode.account-theme-scope .table th,
html[data-theme="dark"] body.account-theme-scope .card,
html[data-theme="dark"] body.account-theme-scope .notification-item,
html[data-theme="dark"] body.account-theme-scope .list-group-item,
html[data-theme="dark"] body.account-theme-scope .modal-content,
html[data-theme="dark"] body.account-theme-scope .dropdown-menu,
html[data-theme="dark"] body.account-theme-scope .form-control,
html[data-theme="dark"] body.account-theme-scope .form-select,
html[data-theme="dark"] body.account-theme-scope .input-group-text,
html[data-theme="dark"] body.account-theme-scope .accordion-item,
html[data-theme="dark"] body.account-theme-scope .table,
html[data-theme="dark"] body.account-theme-scope .table td,
html[data-theme="dark"] body.account-theme-scope .table th,
body[data-theme="dark"].account-theme-scope .card,
body[data-theme="dark"].account-theme-scope .notification-item,
body[data-theme="dark"].account-theme-scope .list-group-item,
body[data-theme="dark"].account-theme-scope .modal-content,
body[data-theme="dark"].account-theme-scope .dropdown-menu,
body[data-theme="dark"].account-theme-scope .form-control,
body[data-theme="dark"].account-theme-scope .form-select,
body[data-theme="dark"].account-theme-scope .input-group-text,
body[data-theme="dark"].account-theme-scope .accordion-item,
body[data-theme="dark"].account-theme-scope .table,
body[data-theme="dark"].account-theme-scope .table td,
body[data-theme="dark"].account-theme-scope .table th,
html[data-bs-theme="dark"] body.account-theme-scope .card,
html[data-bs-theme="dark"] body.account-theme-scope .notification-item,
html[data-bs-theme="dark"] body.account-theme-scope .list-group-item,
html[data-bs-theme="dark"] body.account-theme-scope .modal-content,
html[data-bs-theme="dark"] body.account-theme-scope .dropdown-menu,
html[data-bs-theme="dark"] body.account-theme-scope .form-control,
html[data-bs-theme="dark"] body.account-theme-scope .form-select,
html[data-bs-theme="dark"] body.account-theme-scope .input-group-text,
html[data-bs-theme="dark"] body.account-theme-scope .accordion-item,
html[data-bs-theme="dark"] body.account-theme-scope .table,
html[data-bs-theme="dark"] body.account-theme-scope .table td,
html[data-bs-theme="dark"] body.account-theme-scope .table th,
body[data-bs-theme="dark"].account-theme-scope .card,
body[data-bs-theme="dark"].account-theme-scope .notification-item,
body[data-bs-theme="dark"].account-theme-scope .list-group-item,
body[data-bs-theme="dark"].account-theme-scope .modal-content,
body[data-bs-theme="dark"].account-theme-scope .dropdown-menu,
body[data-bs-theme="dark"].account-theme-scope .form-control,
body[data-bs-theme="dark"].account-theme-scope .form-select,
body[data-bs-theme="dark"].account-theme-scope .input-group-text,
body[data-bs-theme="dark"].account-theme-scope .accordion-item,
body[data-bs-theme="dark"].account-theme-scope .table,
body[data-bs-theme="dark"].account-theme-scope .table td,
body[data-bs-theme="dark"].account-theme-scope .table th {
background-color: #1f2937 !important;
border-color: #374151 !important;
color: #e5e7eb !important;
}
html.dark-mode body.account-theme-scope .card-header,
html.dark-mode body.account-theme-scope .card-header.bg-white,
html.dark-mode body.account-theme-scope .bg-white,
html.dark-mode body.account-theme-scope .account-menu-list .menu-main,
body.dark-mode.account-theme-scope .card-header,
body.dark-mode.account-theme-scope .card-header.bg-white,
body.dark-mode.account-theme-scope .bg-white,
body.dark-mode.account-theme-scope .account-menu-list .menu-main,
html[data-theme="dark"] body.account-theme-scope .card-header,
html[data-theme="dark"] body.account-theme-scope .card-header.bg-white,
html[data-theme="dark"] body.account-theme-scope .bg-white,
html[data-theme="dark"] body.account-theme-scope .account-menu-list .menu-main,
body[data-theme="dark"].account-theme-scope .card-header,
body[data-theme="dark"].account-theme-scope .card-header.bg-white,
body[data-theme="dark"].account-theme-scope .bg-white,
body[data-theme="dark"].account-theme-scope .account-menu-list .menu-main,
html[data-bs-theme="dark"] body.account-theme-scope .card-header,
html[data-bs-theme="dark"] body.account-theme-scope .card-header.bg-white,
html[data-bs-theme="dark"] body.account-theme-scope .bg-white,
html[data-bs-theme="dark"] body.account-theme-scope .account-menu-list .menu-main,
body[data-bs-theme="dark"].account-theme-scope .card-header,
body[data-bs-theme="dark"].account-theme-scope .card-header.bg-white,
body[data-bs-theme="dark"].account-theme-scope .bg-white,
body[data-bs-theme="dark"].account-theme-scope .account-menu-list .menu-main {
background-color: #182131 !important;
border-color: #374151 !important;
}
html.dark-mode body.account-theme-scope .text-muted,
html.dark-mode body.account-theme-scope .form-text,
html.dark-mode body.account-theme-scope .form-label,
html.dark-mode body.account-theme-scope small,
html.dark-mode body.account-theme-scope .small,
html.dark-mode body.account-theme-scope .btn-outline-secondary,
html.dark-mode body.account-theme-scope .btn-outline-secondary i,
body.dark-mode.account-theme-scope .text-muted,
body.dark-mode.account-theme-scope .form-text,
body.dark-mode.account-theme-scope .form-label,
body.dark-mode.account-theme-scope small,
body.dark-mode.account-theme-scope .small,
body.dark-mode.account-theme-scope .btn-outline-secondary,
body.dark-mode.account-theme-scope .btn-outline-secondary i,
html[data-theme="dark"] body.account-theme-scope .text-muted,
html[data-theme="dark"] body.account-theme-scope .form-text,
html[data-theme="dark"] body.account-theme-scope .form-label,
html[data-theme="dark"] body.account-theme-scope small,
html[data-theme="dark"] body.account-theme-scope .small,
html[data-theme="dark"] body.account-theme-scope .btn-outline-secondary,
html[data-theme="dark"] body.account-theme-scope .btn-outline-secondary i,
body[data-theme="dark"].account-theme-scope .text-muted,
body[data-theme="dark"].account-theme-scope .form-text,
body[data-theme="dark"].account-theme-scope .form-label,
body[data-theme="dark"].account-theme-scope small,
body[data-theme="dark"].account-theme-scope .small,
body[data-theme="dark"].account-theme-scope .btn-outline-secondary,
body[data-theme="dark"].account-theme-scope .btn-outline-secondary i,
html[data-bs-theme="dark"] body.account-theme-scope .text-muted,
html[data-bs-theme="dark"] body.account-theme-scope .form-text,
html[data-bs-theme="dark"] body.account-theme-scope .form-label,
html[data-bs-theme="dark"] body.account-theme-scope small,
html[data-bs-theme="dark"] body.account-theme-scope .small,
html[data-bs-theme="dark"] body.account-theme-scope .btn-outline-secondary,
html[data-bs-theme="dark"] body.account-theme-scope .btn-outline-secondary i,
body[data-bs-theme="dark"].account-theme-scope .text-muted,
body[data-bs-theme="dark"].account-theme-scope .form-text,
body[data-bs-theme="dark"].account-theme-scope .form-label,
body[data-bs-theme="dark"].account-theme-scope small,
body[data-bs-theme="dark"].account-theme-scope .small,
body[data-bs-theme="dark"].account-theme-scope .btn-outline-secondary,
body[data-bs-theme="dark"].account-theme-scope .btn-outline-secondary i {
color: #aeb8c7 !important;
}
html.dark-mode body.account-theme-scope .empty-state-icon,
body.dark-mode.account-theme-scope .empty-state-icon,
html[data-theme="dark"] body.account-theme-scope .empty-state-icon,
body[data-theme="dark"].account-theme-scope .empty-state-icon,
html[data-bs-theme="dark"] body.account-theme-scope .empty-state-icon,
body[data-bs-theme="dark"].account-theme-scope .empty-state-icon {
color: #6b7280 !important;
}
</style>
</head>
<body {% if current_menu is defined and current_menu == "listing" %}id="category"{% endif %} class="{% if is_account_route %}account-theme-scope{% endif %}">
<!-- Start Header Area -->
<header class="header_area sticky-header">
<div class="main_menu">
<nav class="navbar navbar-expand-lg navbar-light main_box">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<a class="navbar-brand logo_h" href="{{ path('ui_home') }}"><img style="width: auto;height: 60px" src="{{ asset('logo.png') }}" alt=""></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse offset" id="navbarSupportedContent">
<ul class="nav navbar-nav menu_nav ml-auto">
<li class="nav-item {% if current_menu is defined and current_menu == "home" %}active{% endif %}">
<a class="nav-link" href="{{ path('ui_home') }}">Accueil</a>
</li>
<li class="nav-item {% if current_menu is defined and current_menu == "shops" %}active{% endif %}">
<a class="nav-link" href="{{ path('ui_shops_list') }}">Boutiques</a>
</li>
<li class="nav-item {% if current_menu is defined and current_menu == "listing" %}active{% endif %}">
<a class="nav-link" href="{{ path('ui_listing') }}">Produits</a>
</li>
<li class="nav-item {% if current_menu is defined and current_menu == "blog" %}active{% endif %}">
<a class="nav-link" href="{{ path('ui_blog') }}">Blog</a>
</li>
<li class="nav-item {% if current_menu is defined and current_menu == "contact" %}active{% endif %}">
<a class="nav-link" href="{{ path('ui_contact') }}">Contact</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="nav-item header-icons-item">
<div class="navbar-icons-wrapper">
<a href="{{ path('ui_cart') }}" class="cart {% if current_menu is defined and current_menu == "cart" %}active{% endif %}" aria-label="Panier">
<span class="ti-bag"></span>
</a>
<a href="{{ path('ui_product_comparison') }}" class="comparison-icon {% if current_menu is defined and current_menu == "comparison" %}active{% endif %}" title="Comparaison premium" aria-label="Comparaison premium">
<span class="lnr lnr-sync"></span>
<span class="comparison-badge" id="comparisonBadge" data-comparison-badge style="display: none;">0</span>
</a>
{% if app.user %}
<a href="{{ path('notifications_list') }}" class="notification-icon" title="Notifications" aria-label="Notifications">
<span class="ti-bell"></span>
<span class="notification-badge text-white" id="notificationBadge" style="display: none;color: white;">0</span>
</a>
{% endif %}
<button class="theme-toggle-btn" type="button" data-theme-toggle aria-label="Passer au mode sombre" title="Passer au mode sombre">
<span class="ti-moon" data-theme-icon></span>
</button>
<button class="search" type="button" aria-label="Recherche">
<span class="lnr lnr-magnifier" id="search"></span>
</button>
</div>
</li>
<li class="nav-item header-account-item">
{% if app.user %}
<a class="btn btn-primary btn-account header-account-link text-white" href="{{ path('ui_account_index') }}">
<i class="ti-user"></i> Mon compte
</a>
{% else %}
<a class="btn btn-primary btn-account header-account-link text-white" href="{{ path('ui_app_login') }}">
<i class="ti-lock"></i>Se connecter
</a>
{% endif %}
</li>
</ul>
</div>
</div>
</nav>
</div>
<div class="search_input" id="search_input_box">
<div class="container">
<form class="d-flex justify-content-between">
<input type="text" class="form-control" id="search_input" placeholder="Search Here">
<button type="submit" class="btn"></button>
<span class="lnr lnr-cross" id="close_search" title="Close Search"></span>
</form>
</div>
</div>
</header>
<!-- End Header Area -->
<!-- Flash Messages -->
<div class="container mt-3" id="flash-messages-container" style="position: relative; z-index: 1050;">
<div class="row">
<div class="col-12">
{% for message in app.flashes('success') %}
<div class="alert alert-success alert-dismissible fade show flash-message" role="alert">
<i class="fa fa-check-circle"></i>
<strong>{{ message }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% for message in app.flashes('error') %}
<div class="alert alert-danger alert-dismissible fade show flash-message" role="alert">
<i class="fa fa-exclamation-circle"></i>
<strong>{{ message }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% for message in app.flashes('warning') %}
<div class="alert alert-warning alert-dismissible fade show flash-message" role="alert">
<i class="fa fa-exclamation-triangle"></i>
<strong>{{ message }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% for message in app.flashes('info') %}
<div class="alert alert-info alert-dismissible fade show flash-message" role="alert">
<i class="fa fa-info-circle"></i>
<strong>{{ message }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
</div>
</div>
<div id="comparisonTray" class="comparison-tray" aria-live="polite">
<div class="comparison-tray__header">
<div>
<p class="comparison-tray__title">Comparaison premium</p>
<p class="comparison-tray__meta" id="comparisonTrayMeta">Aucun produit en comparaison</p>
</div>
<a href="{{ path('ui_product_comparison') }}" class="comparison-tray__go">Ouvrir</a>
</div>
<div class="comparison-tray__items" id="comparisonTrayItems"></div>
<p class="comparison-tray__empty" id="comparisonTrayEmpty">Ajoute des produits d'une meme categorie pour obtenir une comparaison ultra pertinente.</p>
<div class="comparison-tray__actions">
<a href="{{ path('ui_product_comparison') }}" class="comparison-tray__go">Comparer maintenant</a>
<button type="button" class="comparison-tray__clear" id="comparisonTrayClear">Vider</button>
</div>
</div>
{% block body %}{% endblock %}
<!-- start footer Area -->
<footer class="footer-area section_gap">
<div class="footer-pattern"></div>
<div class="container">
<div class="row">
<div class="col-lg-3 col-md-6 col-sm-6 mb-4 mb-lg-0">
<div class="single-footer-widget">
<div class="footer-logo mb-3">
<h4 class="footer-brand">MaketOu</h4>
</div>
<p class="footer-description">
MaketOu est la première marketplace haïtienne qui connecte les acheteurs et les vendeurs.
Nous offrons une plateforme sécurisée et moderne pour le commerce en ligne en Haïti.
</p>
<div class="footer-social mt-3">
<h6 class="mb-2">Suivez-nous</h6>
<div class="footer-social-icons">
<a href="#" class="social-icon" aria-label="Facebook" title="Facebook">
<i class="fa fa-facebook"></i>
</a>
<a href="#" class="social-icon" aria-label="Twitter" title="Twitter">
<i class="fa fa-twitter"></i>
</a>
<a href="#" class="social-icon" aria-label="Instagram" title="Instagram">
<i class="fa fa-instagram"></i>
</a>
<a href="#" class="social-icon" aria-label="LinkedIn" title="LinkedIn">
<i class="fa fa-linkedin"></i>
</a>
</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-6 col-sm-6 mb-4 mb-lg-0">
<div class="single-footer-widget">
<h6 class="footer-widget-title">Liens rapides</h6>
<ul class="footer-list">
<li><a href="{{ path('ui_home') }}"><i class="fa fa-angle-right me-2"></i>Accueil</a></li>
<li><a href="{{ path('ui_listing') }}"><i class="fa fa-angle-right me-2"></i>Produits</a></li>
<li><a href="{{ path('ui_shops_list') }}"><i class="fa fa-angle-right me-2"></i>Boutiques</a></li>
{% if app.user %}
<li><a href="{{ path('ui_account_index') }}"><i class="fa fa-angle-right me-2"></i>Mon compte</a></li>
{% else %}
<li><a href="{{ path('ui_app_login') }}"><i class="fa fa-angle-right me-2"></i>Se connecter</a></li>
<li><a href="{{ path('ui_app_register') }}"><i class="fa fa-angle-right me-2"></i>S'inscrire</a></li>
{% endif %}
</ul>
</div>
</div>
<div class="col-lg-2 col-md-6 col-sm-6 mb-4 mb-lg-0">
<div class="single-footer-widget">
<h6 class="footer-widget-title">Pour les vendeurs</h6>
<ul class="footer-list">
<li><a href="{{ path('seller_index') }}"><i class="fa fa-angle-right me-2"></i>Ouvrir une boutique</a></li>
<li><a href="{{ path('seller_help_how_to_sell') }}"><i class="fa fa-angle-right me-2"></i>Comment vendre</a></li>
<li><a href="{{ path('seller_help_pricing') }}"><i class="fa fa-angle-right me-2"></i>Tarifs</a></li>
<li><a href="{{ path('seller_help_support') }}"><i class="fa fa-angle-right me-2"></i>Support vendeur</a></li>
</ul>
</div>
</div>
<div class="col-lg-2 col-md-6 col-sm-6 mb-4 mb-lg-0">
<div class="single-footer-widget">
<h6 class="footer-widget-title">Support</h6>
<ul class="footer-list">
<li><a href="{{ path('ui_help') }}"><i class="fa fa-angle-right me-2"></i> Centre d'Aide</a></li>
<li><a href="{{ path('ui_contact') }}"><i class="fa fa-angle-right me-2"></i> Contactez-nous</a></li>
<li><a href="{{ path('ui_faq') }}"><i class="fa fa-angle-right me-2"></i> FAQ</a></li>
<li><a href="{{ path('ui_privacy') }}"><i class="fa fa-angle-right me-2"></i> Politique de Confidentialité</a></li>
<li><a href="{{ path('ui_terms') }}"><i class="fa fa-angle-right me-2"></i> Conditions d'Utilisation</a></li>
</ul>
</div>
</div>
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-footer-widget">
<h6 class="footer-widget-title">Newsletter</h6>
<p class="footer-newsletter-text">Restez informé des dernières nouveautés</p>
<div class="footer-newsletter-wrapper" id="mc_embed_signup">
<form action="{{ path('ui_newsletter_subscribe') }}" method="post" class="footer-newsletter-form">
<div class="newsletter-input-group">
<i class="fa fa-envelope newsletter-icon"></i>
<input class="form-control newsletter-input" name="email" placeholder="Votre email" required type="email">
<button type="submit" class="newsletter-btn" aria-label="S'abonner">
<span class="newsletter-btn-icon">
<i class="fa fa-paper-plane" aria-hidden="true"></i>
</span>
<span class="newsletter-btn-loader" style="display: none;">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
</button>
</div>
</form>
</div>
<div class="footer-trust-badges mt-3">
<div class="trust-item">
<i class="fa fa-shield"></i>
<span>Paiement sécurisé</span>
</div>
<div class="trust-item">
<i class="fa fa-truck"></i>
<span>Livraison rapide</span>
</div>
</div>
</div>
</div>
</div>
<div class="footer-divider"></div>
<div class="footer-bottom d-flex justify-content-center align-items-center flex-wrap">
<p class="footer-text m-0">
Copyright ©<script>document.write(new Date().getFullYear());</script>
Tous droits réservés | Ce site a été fait avec <i class="fa fa-heart-o" aria-hidden="true" style="color: #dc3545;"></i> par <a
href="https://tech.foulgor.com" target="_blank" style="color: #ffa200;">Foulgor Tech</a>
</p>
</div>
</div>
</footer>
<style>
/* ============================================
FOOTER MODERNE - Design amélioré
============================================ */
.footer-area {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
position: relative;
overflow: hidden;
padding: 50px 0 20px;
}
.footer-pattern {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 20% 50%, rgba(255, 162, 0, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255, 162, 0, 0.08) 0%, transparent 50%);
pointer-events: none;
}
.footer-area .container {
position: relative;
z-index: 1;
}
/* Widgets */
.single-footer-widget {
color: rgba(255, 255, 255, 0.9);
}
.footer-brand {
font-size: 1.6rem;
font-weight: 700;
background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 12px;
display: inline-block;
}
.footer-widget-title {
color: #fff;
font-size: 1rem;
font-weight: 600;
margin-bottom: 18px;
position: relative;
padding-bottom: 8px;
}
.footer-widget-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 40px;
height: 3px;
background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%);
border-radius: 2px;
}
.footer-description {
color: rgba(255, 255, 255, 0.7);
line-height: 1.6;
font-size: 0.9rem;
margin-bottom: 0;
}
/* Liste de liens */
.footer-list {
list-style: none;
padding: 0;
margin: 0;
}
.footer-list li {
margin-bottom: 10px;
}
.footer-list li a {
color: rgba(255, 255, 255, 0.7);
text-decoration: none;
transition: all 0.3s ease;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
}
.footer-list li a i {
font-size: 0.8rem;
opacity: 0.6;
transition: all 0.3s ease;
}
.footer-list li a:hover {
color: #ffa200;
transform: translateX(5px);
}
.footer-list li a:hover i {
opacity: 1;
color: #ffa200;
}
/* Newsletter */
.footer-newsletter-text {
color: rgba(255, 255, 255, 0.7);
font-size: 0.85rem;
margin-bottom: 15px;
line-height: 1.5;
}
.footer-newsletter-wrapper {
margin-bottom: 15px;
}
.newsletter-input-group {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.08);
border-radius: 30px;
padding: 4px 4px 4px 15px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
transition: all 0.3s ease;
gap: 8px;
}
.newsletter-input-group:focus-within {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 162, 0, 0.6);
box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.15);
}
.newsletter-icon {
color: rgba(255, 255, 255, 0.5);
font-size: 0.9rem;
flex-shrink: 0;
}
.newsletter-input-group:focus-within .newsletter-icon {
color: #ffa200;
}
.newsletter-input {
flex: 1;
background: transparent;
border: none;
color: #fff;
padding: 10px 8px;
font-size: 0.9rem;
min-width: 0;
}
.newsletter-input::placeholder {
color: rgba(255, 255, 255, 0.4);
font-size: 0.85rem;
}
.newsletter-input:focus {
outline: none;
box-shadow: none;
}
.newsletter-btn {
background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%);
border: none;
color: white;
width: 42px;
height: 42px;
min-width: 42px;
min-height: 42px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
box-shadow: 0 4px 15px rgba(255, 162, 0, 0.4),
0 0 0 0 rgba(255, 162, 0, 0.4);
font-size: 0.9rem;
position: relative;
overflow: hidden;
}
.newsletter-btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s ease, height 0.6s ease;
}
.newsletter-btn:hover::before {
width: 100%;
height: 100%;
}
.newsletter-btn:hover {
transform: translateX(3px) scale(1.05);
box-shadow: 0 6px 20px rgba(255, 162, 0, 0.5),
0 0 0 8px rgba(255, 162, 0, 0.1);
background: linear-gradient(135deg, #e8910a 0%, #ffa200 100%);
}
.newsletter-btn:active {
transform: translateX(3px) scale(0.98);
box-shadow: 0 3px 10px rgba(255, 162, 0, 0.4);
}
.newsletter-btn:focus {
outline: none;
box-shadow: 0 4px 15px rgba(255, 162, 0, 0.4),
0 0 0 3px rgba(255, 162, 0, 0.3);
}
.newsletter-btn-icon,
.newsletter-btn-loader {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.newsletter-btn:hover .newsletter-btn-icon {
transform: translateX(2px) rotate(-10deg);
}
.newsletter-btn.loading .newsletter-btn-icon {
opacity: 0;
transform: scale(0);
}
.newsletter-btn.loading .newsletter-btn-loader {
display: flex !important;
}
.newsletter-btn.success {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.4);
}
.newsletter-btn.success .newsletter-btn-icon i::before {
content: "\f00c";
}
/* Trust badges */
.footer-trust-badges {
display: flex;
flex-direction: row;
gap: 15px;
flex-wrap: wrap;
}
.trust-item {
display: flex;
align-items: center;
gap: 6px;
color: rgba(255, 255, 255, 0.6);
font-size: 0.8rem;
}
.trust-item i {
color: #ffa200;
font-size: 0.9rem;
}
/* Social icons */
.footer-social {
margin-top: 0 !important;
position: relative !important;
width: 100% !important;
clear: both !important;
float: none !important;
}
.footer-social h6 {
color: #fff;
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 12px !important;
display: block !important;
width: 100% !important;
position: relative !important;
clear: both !important;
float: none !important;
}
.footer-social-icons {
display: flex !important;
gap: 12px !important;
flex-wrap: wrap !important;
align-items: center !important;
justify-content: flex-start !important;
width: 100% !important;
position: relative !important;
clear: both !important;
float: none !important;
margin: 0 !important;
padding: 0 !important;
}
.social-icon {
width: 38px !important;
height: 38px !important;
min-width: 38px !important;
min-height: 38px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
background: rgba(255, 255, 255, 0.1) !important;
border-radius: 50% !important;
color: rgba(255, 255, 255, 0.8) !important;
text-decoration: none !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
font-size: 16px !important;
line-height: 1 !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
flex-shrink: 0 !important;
position: relative !important;
float: none !important;
margin: 0 !important;
padding: 0 !important;
vertical-align: middle !important;
}
.social-icon i {
font-size: 16px !important;
line-height: 1 !important;
display: inline-block !important;
transition: transform 0.3s ease !important;
position: relative !important;
z-index: 1 !important;
margin: 0 !important;
padding: 0 !important;
}
.social-icon:hover {
background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%) !important;
transform: translateY(-5px) scale(1.1) !important;
color: white !important;
border-color: transparent !important;
box-shadow: 0 8px 20px rgba(255, 162, 0, 0.4) !important;
}
.social-icon:hover i {
transform: scale(1.1) !important;
}
/* Footer divider */
.footer-divider {
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
margin: 30px 0 20px;
}
/* Footer bottom */
.footer-bottom {
padding-top: 15px;
}
.footer-text {
color: rgba(255, 255, 255, 0.6);
font-size: 0.9rem;
margin: 0;
text-align: center;
}
/* Responsive */
@media(max-width: 991.98px) {
.footer-area {
padding: 40px 0 20px;
}
.single-footer-widget {
margin-bottom: 30px;
}
.footer-social {
margin-top: 15px;
}
}
@media(max-width: 767.98px) {
.footer-area {
padding: 35px 0 18px;
}
.footer-brand {
font-size: 1.4rem;
}
.footer-widget-title {
font-size: 0.95rem;
}
.newsletter-input-group {
padding: 3px 3px 3px 12px;
}
.newsletter-input {
padding: 8px 6px;
font-size: 0.85rem;
}
.newsletter-btn {
width: 38px;
height: 38px;
min-width: 38px;
min-height: 38px;
font-size: 0.85rem;
}
.footer-trust-badges {
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
gap: 12px;
}
}
@media(max-width: 575.98px) {
.footer-area {
padding: 40px 0 20px;
}
.footer-brand {
font-size: 1.3rem;
}
.social-icon {
width: 38px;
height: 38px;
font-size: 16px;
}
.social-icon i {
font-size: 16px;
}
}
</style>
<script>
// Debounced autocomplete for header search
(function(){
const input = document.getElementById('search_input');
const box = document.getElementById('search_suggestions');
if (!input || !box) return;
let t = null;
input.addEventListener('input', function(){
const q = input.value.trim();
clearTimeout(t);
if (q.length === 0) { box.style.display = 'none'; box.innerHTML=''; return; }
t = setTimeout(() => fetchSuggestions(q), 200);
});
function fetchSuggestions(q){
const url = '{{ path('ui_api_search_suggest') }}' + '?q=' + encodeURIComponent(q);
fetch(url).then(r => r.json()).then(data => {
if (!data.success) { box.style.display='none'; return; }
renderSuggestions(data.results || []);
}).catch(() => { box.style.display='none'; });
}
function renderSuggestions(items){
if (!items.length) { box.style.display='none'; box.innerHTML=''; return; }
box.innerHTML = items.map(it => suggestionItem(it)).join('');
box.style.display = 'block';
}
function suggestionItem(it){
const icon = it.type === 'product' ? 'ti-package' : (it.type === 'shop' ? 'ti-briefcase' : (it.type === 'category' ? 'ti-layers' : 'ti-tag'));
return '<a href="' + it.url + '" class="list-group-item list-group-item-action d-flex align-items-center">'
+ '<i class="' + icon + ' me-4"></i>'
+ '<span class="ml-2">' + escapeHtml(it.label) + '</span>'
+ '<span class="badge bg-light text-dark ms-auto">' + it.type + '</span>'
+ '</a>';
}
function escapeHtml(s){
return s.replace(/[&<>"]+/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c]));
}
document.addEventListener('click', function(e){
if (!box.contains(e.target) && e.target !== input) {
box.style.display='none';
}
});
window.handleSearchSubmit = function(e){
if (e) e.preventDefault();
const first = box.querySelector('a');
if (first) { window.location.href = first.getAttribute('href'); return false; }
// fallback to listing page search by name
const q = input.value.trim();
if (q.length) {
window.location.href = '{{ path('ui_listing') }}' + '?q=' + encodeURIComponent(q);
}
return false;
}
})();
// Gestion des mega-menus avec clic (comme la barre de recherche)
document.addEventListener('DOMContentLoaded', function() {
const dropdownItems = document.querySelectorAll('.nav-item.dropdown');
dropdownItems.forEach(function(item) {
const dropdownToggle = item.querySelector('.dropdown-toggle');
const dropdownMenu = item.querySelector('.mega-menu');
if (!dropdownToggle || !dropdownMenu) return;
// Fermer les autres menus quand on ouvre un nouveau
function closeAllMenus() {
dropdownItems.forEach(function(otherItem) {
const otherMenu = otherItem.querySelector('.mega-menu');
if (otherMenu && otherMenu !== dropdownMenu) {
$(otherMenu).slideUp(300);
otherMenu.classList.remove('active');
}
});
}
// Toggle au clic (comme la barre de recherche)
dropdownToggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
closeAllMenus();
if (dropdownMenu.classList.contains('active')) {
// Fermer le menu
dropdownMenu.style.opacity = '0';
dropdownMenu.style.visibility = 'hidden';
dropdownMenu.style.transform = 'translateX(-50%) translateY(-10px)';
setTimeout(() => {
dropdownMenu.classList.remove('active');
dropdownMenu.style.display = 'none';
}, 300);
} else {
// Ouvrir le menu
dropdownMenu.style.display = 'block';
setTimeout(() => {
dropdownMenu.classList.add('active');
dropdownMenu.style.opacity = '1';
dropdownMenu.style.visibility = 'visible';
dropdownMenu.style.transform = 'translateX(-50%) translateY(0)';
}, 10);
}
});
// Fermer au clic en dehors
document.addEventListener('click', function(e) {
if (!item.contains(e.target) && dropdownMenu.classList.contains('active')) {
dropdownMenu.style.opacity = '0';
dropdownMenu.style.visibility = 'hidden';
dropdownMenu.style.transform = 'translateX(-50%) translateY(-10px)';
setTimeout(() => {
dropdownMenu.classList.remove('active');
dropdownMenu.style.display = 'none';
}, 300);
}
});
// Animation des éléments du menu
const menuItems = dropdownMenu.querySelectorAll('.dropdown-item a');
menuItems.forEach(function(menuItem, index) {
menuItem.style.animationDelay = (index * 0.05) + 's';
});
});
// Gestion des clics sur mobile
if (window.innerWidth <= 768) {
dropdownItems.forEach(function(item) {
const dropdownToggle = item.querySelector('.dropdown-toggle');
const dropdownMenu = item.querySelector('.mega-menu');
dropdownToggle.addEventListener('click', function(e) {
e.preventDefault();
// Fermer tous les autres menus
dropdownItems.forEach(function(otherItem) {
if (otherItem !== item) {
const otherMenu = otherItem.querySelector('.mega-menu');
otherMenu.style.opacity = '0';
otherMenu.style.visibility = 'hidden';
otherMenu.style.transform = 'translateY(-10px)';
}
});
// Toggle le menu actuel
if (dropdownMenu.style.opacity === '1') {
dropdownMenu.style.opacity = '0';
dropdownMenu.style.visibility = 'hidden';
dropdownMenu.style.transform = 'translateY(-10px)';
} else {
dropdownMenu.style.opacity = '1';
dropdownMenu.style.visibility = 'visible';
dropdownMenu.style.transform = 'translateY(0)';
}
});
});
}
// Fermer les menus quand on clique ailleurs
document.addEventListener('click', function(e) {
if (!e.target.closest('.nav-item.dropdown')) {
dropdownItems.forEach(function(item) {
const dropdownMenu = item.querySelector('.mega-menu');
dropdownMenu.style.opacity = '0';
dropdownMenu.style.visibility = 'hidden';
dropdownMenu.style.transform = 'translateY(-10px)';
});
}
});
});
</script>
<!-- End footer Area -->
<script src="{{ asset('ui/js/vendor/jquery-2.2.4.min.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Initialiser le collapse Bootstrap 5 pour le menu hamburger après chargement de Bootstrap
document.addEventListener('DOMContentLoaded', function() {
// Attendre que Bootstrap soit complètement chargé
if (typeof bootstrap !== 'undefined') {
const navbarToggler = document.querySelector('.navbar-toggler');
const navbarCollapse = document.getElementById('navbarSupportedContent');
if (navbarToggler && navbarCollapse) {
// Bootstrap 5 devrait gérer automatiquement avec data-bs-toggle
// Mais on s'assure que ça fonctionne en écoutant les événements
navbarCollapse.addEventListener('show.bs.collapse', function() {
navbarToggler.setAttribute('aria-expanded', 'true');
navbarToggler.classList.add('active');
});
navbarCollapse.addEventListener('hide.bs.collapse', function() {
navbarToggler.setAttribute('aria-expanded', 'false');
navbarToggler.classList.remove('active');
});
}
}
});
</script>
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
<script src="{{ asset('ui/js/jquery.ajaxchimp.min.js') }}"></script>
<script src="{{ asset('ui/js/jquery.nice-select.min.js') }}"></script>
<script src="{{ asset('ui/js/jquery.sticky.js') }}"></script>
<script src="{{ asset('ui/js/nouislider.min.js') }}"></script>
<script src="{{ asset('ui/js/countdown.js') }}"></script>
<script src="{{ asset('ui/js/jquery.magnific-popup.min.js') }}"></script>
<script src="{{ asset('ui/js/owl.carousel.min.js') }}"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCjCGmQ0Uq4exrzdcL6rvxywDDOvfAu6eE"></script>
<script src="{{ asset('ui/js/gmaps.min.js') }}"></script>
<script src="{{ asset('ui/js/main.js') }}"></script>
<script>
window.maketouConfig = {
themeStorageKey: 'maketou-theme',
comparison: {
assetBase: '{{ asset('')|e('js') }}',
addUrlTemplate: '{{ path('ui_api_comparison_add', {'id': '__ID__'})|e('js') }}',
removeUrlTemplate: '{{ path('ui_api_comparison_remove', {'id': '__ID__'})|e('js') }}',
clearUrl: '{{ path('ui_api_comparison_clear')|e('js') }}',
countUrl: '{{ path('ui_api_comparison_count')|e('js') }}',
dataUrl: '{{ path('ui_api_comparison_data')|e('js') }}',
pageUrl: '{{ path('ui_product_comparison')|e('js') }}'
}
};
</script>
<script src="{{ asset('ui/js/theme-public.js') }}"></script>
<script src="{{ asset('ui/js/comparison-v2.js') }}"></script>
{{ encore_entry_script_tags('app') }}
<!-- PWA Service Worker Registration -->
<script>
// Enregistrement du Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('{{ asset('sw.js') }}')
.then((registration) => {
console.log('Service Worker enregistré avec succès:', registration.scope);
// Vérifie les mises à jour du service worker
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// Nouveau service worker disponible
if (confirm('Une nouvelle version de MaketOu est disponible. Voulez-vous l\'installer maintenant ?')) {
newWorker.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
}
});
});
})
.catch((error) => {
console.log('Échec de l\'enregistrement du Service Worker:', error);
});
// Écoute les messages du service worker
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('Message du Service Worker:', event.data);
});
});
}
</script>
<!-- PWA Install Script -->
<script src="{{ asset('js/pwa-install.js') }}"></script>
<script>
// Charger les compteurs au chargement de la page
document.addEventListener('DOMContentLoaded', function() {
{% if app.user %}
loadNotificationCount();
// Délai pour s'assurer que la session est bien établie après connexion
setTimeout(function() {
loadComparisonCount();
}, 500);
{% endif %}
// Gérer le bouton de recherche mobile
const searchMobileBtn = document.getElementById('searchMobileBtn');
if (searchMobileBtn) {
searchMobileBtn.addEventListener('click', function(e) {
e.preventDefault();
// Utiliser le même comportement que le bouton desktop
const searchBox = document.getElementById('search_input_box');
if (searchBox) {
searchBox.style.display = 'block';
const searchInput = document.getElementById('search_input');
if (searchInput) {
setTimeout(() => searchInput.focus(), 100);
}
}
});
}
});
function loadNotificationCount() {
{% if app.user %}
fetch('{{ path("api_notifications_count") }}')
.then(response => {
if (!response.ok) {
throw new Error('Erreur HTTP: ' + response.status);
}
return response.json();
})
.then(data => {
const count = data.count || 0;
// Mettre à jour tous les badges de notification
const badges = [
document.getElementById('notificationBadge'),
document.getElementById('notificationBadgeMobile'),
document.querySelector('.notification-badge')
].filter(badge => badge !== null);
badges.forEach(badge => {
if (count > 0) {
badge.textContent = count;
badge.style.display = 'flex';
badge.style.color = 'white';
badge.style.backgroundColor = '#dc3545';
} else {
badge.style.display = 'none';
}
});
// Si on est sur la page des notifications, mettre à jour aussi le contenu de la page
if (typeof updateNotificationPageContent === 'function') {
updateNotificationPageContent(count);
}
})
.catch(error => {
console.error('Erreur lors du chargement du compteur de notifications:', error);
});
{% endif %}
}
// Exposer la fonction globalement pour qu'elle puisse être appelée depuis d'autres pages
window.loadNotificationCount = loadNotificationCount;
// Recharger le compteur toutes les 30 secondes pour garder les données à jour
{% if app.user %}
setInterval(function() {
loadNotificationCount();
}, 30000);
{% endif %}
function loadComparisonCount() {
// Ne pas appeler l'API si on est sur une page d'erreur ou de redirection
if (window.location.pathname.includes('/api/') || window.location.pathname.includes('/login')) {
return;
}
// Utiliser un try-catch pour éviter toute redirection
try {
fetch('{{ path("ui_api_comparison_count") }}', {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
},
credentials: 'same-origin',
redirect: 'error' // Empêcher les redirections automatiques
})
.then(response => {
// Vérifier si la réponse est OK
if (!response.ok) {
// Si c'est une redirection (301, 302, etc.) ou une erreur d'authentification, ignorer silencieusement
if (response.status >= 300 && response.status < 400) {
console.warn('Redirection détectée, arrêt de la requête');
return null;
}
if (response.status === 401 || response.status === 403) {
console.warn('Utilisateur non authentifié pour la comparaison');
return null;
}
// Pour les autres erreurs, throw pour être capturé par catch
throw new Error('Erreur HTTP: ' + response.status);
}
// Vérifier que c'est bien du JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
// Si ce n'est pas du JSON, c'est probablement une redirection HTML
console.warn('La réponse n\'est pas du JSON, probable redirection');
return null;
}
return response.json();
})
.then(data => {
// Si data est null (redirection ou erreur), ne rien faire
if (!data || typeof data.count === 'undefined') {
return;
}
// Mettre à jour les badges desktop et mobile
const badges = [
document.getElementById('comparisonBadge'),
document.getElementById('comparisonBadgeMobile')
];
badges.forEach(badge => {
if (badge) {
if (data.count > 0) {
badge.textContent = data.count;
badge.style.display = 'flex';
} else {
badge.style.display = 'none';
}
}
});
})
.catch(error => {
// Ne pas afficher d'erreur si c'est juste une absence d'authentification ou une redirection
if (error.message && !error.message.includes('401') && !error.message.includes('403') && !error.message.includes('redirect')) {
console.error('Erreur lors du chargement du compteur de comparaison:', error);
}
// Empêcher toute redirection vers l'URL de l'API
return false;
});
} catch (e) {
// Capturer toute erreur qui pourrait causer une redirection
console.warn('Erreur lors de l\'appel à l\'API de comparaison:', e);
}
}
// Fonction globale pour gérer la comparaison
function toggleComparison(productId, element) {
if (!element) {
element = document.querySelector(`.comparison-btn[data-product-id="${productId}"]`);
}
if (!element) {
console.error('Bouton de comparaison non trouvé pour le produit', productId);
return;
}
const originalHtml = element.innerHTML;
// Désactiver le bouton/lien
if (element.tagName === 'BUTTON') {
element.disabled = true;
} else {
element.style.pointerEvents = 'none';
element.style.opacity = '0.6';
}
element.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
// Vérifier d'abord si le produit est déjà en comparaison
let isInComparison = false;
fetch(`/api/comparison/status/${productId}`)
.then(response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('text/html')) {
window.location.href = '{{ path("ui_app_login") }}';
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
isInComparison = data.inComparison;
const url = isInComparison ? `/api/comparison/remove/${productId}` : `/api/comparison/add/${productId}`;
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
})
.then(response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('text/html')) {
window.location.href = '{{ path("ui_app_login") }}';
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
if (data.success) {
// Mettre à jour le compteur
loadComparisonCount();
// Mettre à jour l'apparence du bouton
if (element) {
// Inverser l'état (car on vient de le changer)
const newState = !isInComparison;
// Restaurer le HTML original puis mettre à jour
element.innerHTML = originalHtml;
if (newState) {
element.classList.add('active');
// Mettre à jour le texte pour les deux types de boutons
const textEl = element.querySelector('.comparison-text') || element.querySelector('.hover-text') || element.querySelector('p.hover-text');
if (textEl) textEl.textContent = 'En comparaison';
// Mettre à jour l'icône si nécessaire (chercher dans span ou i)
const iconEl = element.querySelector('.lnr-sync, span.lnr-sync, [class*="sync"], i[class*="sync"]');
if (iconEl) {
iconEl.style.color = '#28a745';
iconEl.classList.add('active');
}
} else {
element.classList.remove('active');
const textEl = element.querySelector('.comparison-text') || element.querySelector('.hover-text') || element.querySelector('p.hover-text');
if (textEl) textEl.textContent = 'Comparer';
// Restaurer la couleur de l'icône
const iconEl = element.querySelector('.lnr-sync, span.lnr-sync, [class*="sync"], i[class*="sync"]');
if (iconEl) {
iconEl.style.color = '';
iconEl.classList.remove('active');
}
}
}
// Afficher une notification
showNotification(data.message, 'success');
} else {
showNotification(data.message, 'error');
if (element) {
element.innerHTML = originalHtml;
}
}
})
.catch(error => {
console.error('Erreur:', error);
showNotification('Une erreur est survenue', 'error');
if (element) {
element.innerHTML = originalHtml;
}
})
.finally(() => {
if (element) {
if (element.tagName === 'BUTTON') {
element.disabled = false;
} else {
element.style.pointerEvents = '';
element.style.opacity = '';
}
}
});
}
// Fonction globale pour gérer les favoris
function toggleWishlist(productId, element) {
if (!element) {
element = document.querySelector(`.wishlist-btn[data-product-id="${productId}"]`);
}
if (!element) {
console.error('Bouton de wishlist non trouvé pour le produit', productId);
return;
}
const originalHtml = element.innerHTML;
// Désactiver le bouton/lien
if (element.tagName === 'BUTTON') {
element.disabled = true;
} else {
element.style.pointerEvents = 'none';
element.style.opacity = '0.6';
}
element.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
// Vérifier d'abord si le produit est déjà en favoris
let isInWishlist = false;
fetch(`/api/wishlist/status/${productId}`)
.then(response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('text/html')) {
window.location.href = '{{ path("ui_app_login") }}';
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
isInWishlist = data.inWishlist;
const url = isInWishlist ? `/api/wishlist/remove/${productId}` : `/api/wishlist/add/${productId}`;
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
})
.then(response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('text/html')) {
window.location.href = '{{ path("ui_app_login") }}';
throw new Error('Not authenticated');
}
return response.json();
})
.then(data => {
if (data.success) {
// Mettre à jour l'apparence du bouton
if (element) {
// Inverser l'état (car on vient de le changer)
const newState = !isInWishlist;
// Restaurer le HTML original puis mettre à jour
element.innerHTML = originalHtml;
// Chercher l'icône dans span ou i
const icon = element.querySelector('span.lnr-heart, i.lnr-heart, [class*="lnr-heart"]');
const text = element.querySelector('.wishlist-text') || element.querySelector('.hover-text') || element.querySelector('p.hover-text');
if (newState) {
if (icon) {
// Pour les icônes linr, on change la classe
if (icon.classList.contains('lnr-heart')) {
icon.classList.remove('lnr-heart');
}
if (!icon.classList.contains('lnr-heart-filled')) {
icon.classList.add('lnr-heart-filled');
}
icon.style.color = '#dc3545';
}
element.classList.add('active');
if (text) text.textContent = 'Dans favoris';
} else {
if (icon) {
// Restaurer l'icône originale
if (icon.classList.contains('lnr-heart-filled')) {
icon.classList.remove('lnr-heart-filled');
}
if (!icon.classList.contains('lnr-heart')) {
icon.classList.add('lnr-heart');
}
icon.style.color = '';
}
element.classList.remove('active');
if (text) text.textContent = 'Favoris';
}
}
// Afficher une notification
showNotification(data.message, 'success');
// Si on est sur la page wishlist, recharger la page
if (window.location.pathname.includes('/account/wishlist')) {
setTimeout(() => location.reload(), 1000);
}
} else {
showNotification(data.message, 'error');
if (element) {
element.innerHTML = originalHtml;
}
}
})
.catch(error => {
console.error('Erreur:', error);
showNotification('Une erreur est survenue', 'error');
if (element) {
element.innerHTML = originalHtml;
}
})
.finally(() => {
if (element) {
if (element.tagName === 'BUTTON') {
element.disabled = false;
} else {
element.style.pointerEvents = '';
element.style.opacity = '';
}
}
});
}
// Fonction pour afficher des notifications
function showNotification(message, type = 'success') {
// Créer l'élément de notification
const notification = document.createElement('div');
notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show position-fixed`;
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Supprimer automatiquement après 3 secondes
setTimeout(() => {
notification.remove();
}, 3000);
}
// Initialiser les alertes Bootstrap 5 pour la fermeture
document.addEventListener('DOMContentLoaded', function() {
// S'assurer que les alertes peuvent être fermées
const alertCloseButtons = document.querySelectorAll('.flash-message .btn-close');
alertCloseButtons.forEach(function(button) {
button.addEventListener('click', function() {
const alert = this.closest('.alert');
if (alert) {
alert.classList.remove('show');
setTimeout(function() {
alert.remove();
}, 150);
}
});
});
// Charger les statuts initiaux des produits
{% if app.user %}
// Charger les statuts de comparaison pour tous les produits visibles
document.querySelectorAll('.comparison-btn[data-product-id]').forEach(btn => {
const productId = btn.dataset.productId;
fetch(`/api/comparison/status/${productId}`)
.then(response => response.json())
.then(data => {
if (data.inComparison) {
btn.classList.add('active');
// Mettre à jour le texte pour les deux types de boutons
const textEl = btn.querySelector('.comparison-text') || btn.querySelector('.hover-text') || btn.querySelector('p.hover-text');
if (textEl) textEl.textContent = 'En comparaison';
// Mettre à jour l'icône si nécessaire
const iconEl = btn.querySelector('.lnr-sync, [class*="sync"]');
if (iconEl) iconEl.style.color = '#28a745';
}
})
.catch(() => {});
});
// Charger les statuts de wishlist pour tous les produits visibles
document.querySelectorAll('.wishlist-btn[data-product-id]').forEach(btn => {
const productId = btn.dataset.productId;
fetch(`/api/wishlist/status/${productId}`)
.then(response => response.json())
.then(data => {
if (data.inWishlist) {
btn.classList.add('active');
// Chercher l'icône dans span ou i
const icon = btn.querySelector('span.lnr-heart, i.lnr-heart, [class*="lnr-heart"]');
if (icon) {
icon.classList.remove('lnr-heart');
icon.classList.add('lnr-heart-filled');
icon.style.color = '#dc3545';
}
// Mettre à jour le texte pour les deux types de boutons
const textEl = btn.querySelector('.wishlist-text') || btn.querySelector('.hover-text') || btn.querySelector('p.hover-text');
if (textEl) textEl.textContent = 'Dans favoris';
}
})
.catch(() => {});
});
// Charger le compteur de comparaison
loadComparisonCount();
{% endif %}
});
</script>
<script>
// Loader global pour tous les boutons de formulaire
document.addEventListener('DOMContentLoaded', function() {
// Intercepter tous les formulaires
const forms = document.querySelectorAll('form');
forms.forEach(function(form) {
form.addEventListener('submit', function(e) {
const submitButtons = form.querySelectorAll('button[type="submit"], input[type="submit"], button:not([type])');
submitButtons.forEach(function(button) {
// Vérifier si le bouton n'est pas déjà désactivé
if (!button.disabled && !button.classList.contains('loading')) {
button.classList.add('loading');
button.disabled = true;
// Sauvegarder le texte original
const originalText = button.innerHTML;
button.setAttribute('data-original-text', originalText);
// Ajouter le loader
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Envoi...';
// Si le formulaire n'est pas valide, réactiver le bouton après 1 seconde
setTimeout(function() {
if (!form.checkValidity()) {
button.classList.remove('loading');
button.disabled = false;
button.innerHTML = originalText;
}
}, 1000);
}
});
});
});
// Pour les boutons qui déclenchent des actions AJAX
const ajaxButtons = document.querySelectorAll('[data-ajax], .ajax-submit, button[data-submit]');
ajaxButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
if (this.disabled || this.classList.contains('loading')) {
e.preventDefault();
return false;
}
this.classList.add('loading');
this.disabled = true;
const originalText = this.innerHTML;
this.setAttribute('data-original-text', originalText);
this.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Chargement...';
});
});
// Gestionnaire spécifique pour le formulaire de newsletter
const newsletterForm = document.querySelector('.footer-newsletter-form');
if (newsletterForm) {
newsletterForm.addEventListener('submit', function(e) {
const submitBtn = this.querySelector('.newsletter-btn');
if (submitBtn && !submitBtn.disabled) {
submitBtn.classList.add('loading');
submitBtn.disabled = true;
// Afficher le loader et masquer l'icône
const icon = submitBtn.querySelector('.newsletter-btn-icon');
const loader = submitBtn.querySelector('.newsletter-btn-loader');
if (icon) icon.style.display = 'none';
if (loader) loader.style.display = 'flex';
// Réactiver le bouton après 5 secondes au cas où la requête échoue silencieusement
setTimeout(function() {
if (submitBtn.classList.contains('loading')) {
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
if (icon) icon.style.display = 'flex';
if (loader) loader.style.display = 'none';
}
}, 5000);
}
});
// Gérer la réponse du formulaire (si AJAX)
newsletterForm.addEventListener('submit', function(e) {
// Si le formulaire est soumis normalement (non-AJAX), le navigateur gère la réponse
// Sinon, on peut intercepter pour gérer la réponse AJAX ici
});
}
});
// CSS pour le spinner
const style = document.createElement('style');
style.textContent = `
.spinner-border-sm {
width: 1rem;
height: 1rem;
border-width: 0.15em;
}
.spinner-border {
display: inline-block;
width: 1rem;
height: 1rem;
vertical-align: text-bottom;
border: 0.15em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spinner-border 0.75s linear infinite;
}
@keyframes spinner-border {
to { transform: rotate(360deg); }
}
button.loading {
opacity: 0.7;
cursor: not-allowed !important;
}
`;
document.head.appendChild(style);
</script>
<!-- Modal Global Custom Alert -->
<div class="modal fade custom-alert-modal" id="customAlertModal" tabindex="-1" aria-labelledby="customAlertModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-white" id="customAlertModalLabel">Notification</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert-icon" id="alertIcon">
<i class="lnr lnr-info-circle"></i>
</div>
<p class="alert-message" id="alertMessage"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<script>
// Fonction globale pour remplacer alert() ou afficher des messages
function showCustomAlert(message, type = 'info') {
const modalEl = document.getElementById('customAlertModal');
if (!modalEl) {
// Fallback sur showNotification
let notifyType = type === 'error' || type === 'danger' ? 'error' : (type === 'success' ? 'success' : 'warning');
if (typeof showNotification === 'function') {
showNotification(message, notifyType);
} else {
alert(message);
}
return;
}
const modal = new bootstrap.Modal(modalEl);
const alertMessage = document.getElementById('alertMessage');
const alertIcon = document.getElementById('alertIcon');
const modalTitle = document.getElementById('customAlertModalLabel');
alertMessage.textContent = message;
// Définir l'icône et le titre selon le type
let iconClass = 'lnr lnr-info-circle';
let title = 'Information';
switch (type) {
case 'success':
iconClass = 'lnr lnr-checkmark-circle';
title = 'Succès';
alertIcon.className = 'alert-icon success';
break;
case 'error':
case 'danger':
iconClass = 'lnr lnr-warning';
title = 'Erreur';
alertIcon.className = 'alert-icon error';
break;
case 'warning':
iconClass = 'lnr lnr-warning';
title = 'Attention';
alertIcon.className = 'alert-icon warning';
break;
default:
iconClass = 'lnr lnr-info-circle';
title = 'Information';
alertIcon.className = 'alert-icon info';
}
alertIcon.innerHTML = `<i class="${iconClass}"></i>`;
modalTitle.textContent = title;
modal.show();
};
</script>
{% block javascripts %}
{% endblock %}
</body>
</html>