{% extends "Parent/LayoutParent.html.twig" %} {% block LinksCss %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.js" defer></script>
<link rel="stylesheet" href="{{ '/css/Parent/css/premiercnx.css' }}" />
<link href="{{ asset('css/Parent/css/detailsejour.css') }}" type="text/css" rel="stylesheet" />
<link rel="stylesheet" href="{{ '/css/Accompagnateur/imgzoom.css' }}" />
<link rel="stylesheet" href="{{ asset('Plugins/css/dropzone.css') }}" />
<link rel="stylesheet" href="{{ asset('css/splide.min.css') }}" />
<link rel="stylesheet" href="{{ asset('css/favorites-sidebar.css') }}" />
{% set destination = "detailsejour" %}
<style>
.slide img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
transition: transform 0.3s ease;
cursor: pointer;
}
.slide img.zoomed {
transform: scale(1.5);
cursor: zoom-in;
}
</style>
{% endblock %} {% set pageMenu = app.session.get('pageMenu') %} {% block Content
%}
<!-- Alerte pour inciter à acheter -->
<div id="purchase-alert" class="purchase-alert hidden" style="display: none;">
<button class="close-btn" onclick="closePurchaseAlert()">×</button>
<div id="purchase-alert-content">
<!-- Le contenu de l'alerte sera mis à jour dynamiquement en fonction du nombre de favoris -->
</div>
</div>
<div
id="verifImg"
class="modal fade"
role="dialog"
style="background-color: rgba(112, 112, 112, 0.56); z-index: 1000000"
>
</div>
<div class="main-content">
<div class="row no-margin">
<!-- Bouton cadeau attractif e-commerce -->
<div
class="gift-button"
id="gift-button-trigger"
style="cursor: pointer;"
>
<i id="gift-icon-payment" class="bi bi-gift gift-pulse"></i>
<label
id="giftCount"
class="labelGiftCount gift-count-bounce"
style="background-color: #f56040"
>
{{ likes | length }}
</label>
<!-- Message flottant attractif -->
<div class="gift-tooltip" id="giftTooltip">
<div class="gift-tooltip-content">
<div class="gift-tooltip-header">
<i class="bi bi-clock-history"></i>
<strong>Offre limitée !</strong>
</div>
<p>Commandez vite ! Les photos seront disponibles seulement pendant <strong>6 semaines</strong> après la fin du séjour.</p>
<div class="gift-tooltip-cta">
<i class="bi bi-lightning-charge"></i>
Commander maintenant
</div>
</div>
</div>
</div>
<!-- Sidebar E-commerce -->
<div id="ecommerce-sidebar">
<div class="ecommerce-header">
<h2 class="ecommerce-title" id="ecommerce-title">
🎉 Vos souvenirs favoris sont prêts !
</h2>
<p class="ecommerce-subtitle" id="ecommerce-subtitle">
{{ likes | length }} photos sélectionnées avec amour ❤️
</p>
<button class="ecommerce-close" onclick="closeEcommerceSidebar()">
<i class="bi bi-x"></i>
</button>
</div>
<div class="ecommerce-content">
<!-- Bannière d'urgence -->
<div class="urgency-banner" id="urgency-banner">
<div>
<i class="bi bi-clock-history"></i>
<strong>⏳ Commandez vite !</strong>
</div>
<div>Vos photos seront disponibles seulement <strong>6 semaines</strong> après la fin du séjour</div>
<div class="countdown" id="countdown-timer" style="display: none;">
Plus que <span id="days-left">0</span> jours !
</div>
</div>
<!-- Produits recommandés -->
<div id="products-container">
<!-- Album Photo -->
<div class="product-card popular">
<div class="product-header">
<div class="product-icon">
<img src="/images/produit/Album5sur5-3.jpg" alt="Album Photo Premium" style="width: 100%; height: 100%; object-fit: contain; border-radius: 8px;">
</div>
<div class="product-info">
<h4>Album Photo Premium</h4>
<div class="product-pricing">
<span class="current-price">24,90€</span>
<span class="original-price">28,90€</span>
<span class="savings">-14%</span>
</div>
</div>
<div class="product-favorites-indicator" title="Photos favorites sélectionnées">
<i class="bi bi-heart-fill" style="color: #f56040"></i>
<span id="album-fav-count">{{ likes | length }}</span>
</div>
</div>
<p class="product-description">
Album photo personnalisé avec vos <strong id="album-count">{{ likes | length }}</strong> photos favorites. Papier de qualité supérieure, reliure rigide.
</p>
<button class="product-cta" onclick="orderProduct('album')"
title="Commander l'album photo avec vos photos favorites">
<i class="bi bi-cart-plus"></i>
Commander l'album
</button>
</div>
<!-- Pack Numérique -->
<div class="product-card">
<div class="product-header">
<div class="product-icon">
<img src="/images/produit/photoNumerique.jpg" alt="Pack Numérique HD" style="width: 100%; height: 100%; object-fit: contain; border-radius: 8px;">
</div>
<div class="product-info">
<h4>Pack Numérique HD</h4>
<p class="price">À partir de 3,90€</p>
</div>
<div class="product-favorites-indicator" title="Photos favorites sélectionnées">
<i class="bi bi-heart-fill" style="color: #f56040"></i>
<span id="digital-fav-count">{{ likes | length }}</span>
</div>
</div>
<p class="product-description">
Téléchargement immédiat de vos <strong id="digital-count">{{ likes | length }}</strong> photos en haute définition. Qualité professionnelle garantie.
</p>
<button class="product-cta" onclick="orderProduct('digital')"
title="Télécharger vos photos en haute définition">
<i class="bi bi-download"></i>
Télécharger HD
</button>
</div>
<!-- Pochette Tirages -->
<div class="product-card" id="prints-card" style="display: none;">
<div class="product-header">
<div class="product-icon">
<i class="bi bi-images"></i>
</div>
<div class="product-info">
<h4>Pochette Tirages</h4>
<p class="price">À partir de 9,90€</p>
</div>
</div>
<p class="product-description">
<strong id="prints-count">12</strong> tirages photo 10x15cm de vos favoris. Papier brillant professionnel, envoi sous 48h.
</p>
<button class="product-cta" onclick="orderProduct('prints')"
title="Commander des tirages photo de vos favoris">
<i class="bi bi-printer"></i>
Commander les tirages
</button>
</div>
</div>
<!-- Réassurance -->
<div class="reassurance">
<i class="bi bi-shield-check" style="color: #10b981; margin-right: 8px;"></i>
<strong>Imprimé par l'équipe 5sur5 – satisfaction garantie ✅</strong>
</div>
</div>
</div>
<div class="selection-popover" id="selectionPopover">
<h4>Votre sélection</h4>
<p>Tirages : {{ likes | length }} / 12</p>
<p>Numériques : {{ likes | length }} / 15</p>
<p>Album : {{ likes | length }} / 20</p>
<button class="finalize-button" onclick="openEcommerceSidebar()"
title="Voir les produits disponibles avec vos favoris">
<i class="bi bi-gift"></i>
Finaliser ma commande
</button>
</div>
<style>
/* Amélioration des boutons de produits */
.product-cta {
background: #3a8d95;
border: none;
border-radius: 25px;
color: white;
padding: 12px 24px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
width: 100%;
justify-content: center;
margin-top: 10px;
}
.product-cta:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(58, 141, 149, 0.4);
background: #2f7a82;
}
.product-cta:active {
transform: translateY(0);
}
.product-cta:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.finalize-button {
background: linear-gradient(135deg, #e91e63 0%, #ad1457 100%);
border: none;
border-radius: 25px;
color: white;
padding: 12px 24px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
width: 100%;
justify-content: center;
margin-top: 15px;
}
.finalize-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(233, 30, 99, 0.4);
background: linear-gradient(135deg, #d81b60 0%, #9c1450 100%);
}
/* Amélioration des cartes de produits */
.product-card {
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.3s ease;
margin-bottom: 20px;
overflow: hidden;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}
.product-header {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background: #f8f9fa;
}
.product-icon {
width: 60px;
height: 60px;
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
}
.product-info h4 {
margin: 0 0 5px 0;
color: #333;
font-weight: 600;
}
.product-pricing {
display: flex;
align-items: center;
gap: 10px;
margin: 5px 0;
}
.current-price {
font-size: 1.2rem;
font-weight: 700;
color: #e91e63;
}
.original-price {
text-decoration: line-through;
color: #666;
font-size: 0.9rem;
}
.savings {
background: #4caf50;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
}
.product-description {
padding: 0 20px 20px 20px;
color: #666;
line-height: 1.5;
}
.product-favorites-indicator {
display: flex;
align-items: center;
gap: 5px;
background: rgba(245, 96, 64, 0.1);
padding: 5px 10px;
border-radius: 15px;
font-size: 0.9rem;
color: #f56040;
font-weight: 600;
}
</style>
</div>
<div class="divSliderModern">
<input type="hidden" id="nbFavCurrent" value="{{ nblikes }}" />
<input id="likeCount" type="hidden" value="{{ nblikes }}" />
<div
class="splide no-padding no-margin"
id="imageSlider"
style="max-height: 200px"
>
<div class="splide__track">
<ul class="splide__list">
<!-- Slide 1 -->
<li class="splide__slide">
<div class="slider-content" style="background: white">
<div class="namePRD" style="display: block">
<h4
class="titleProdbienvenu titleProdbienvenu1"
style="color: #41a2aa"
>
Ajoutez vos favoris dès maintenant
</h4>
<h4
class="titleProdbienvenu titleProdbienvenu2"
style="color: #f09e7a"
>
et profitez de nos produits souvenirs personnalisés !
</h4>
</div>
<img
src="{{ asset('/images/imgSliderEmpty2.png') }}"
class="imgslider"
alt="Image 1"
/>
</div>
</li>
<!-- Slide 2 -->
<li class="splide__slide">
<div class="slider-content" style="background: white">
<div class="namePRD" style="display: block">
<h4
class="titleProdbienvenu titleProdbienvenu1"
style="color: #f09e7a"
>
Pensez à commander le livre du séjour
</h4>
<h4
class="titleProdbienvenu titleProdbienvenu2"
style="color: #41a2aa"
>
et offrez lui le plus beau des cadeaux !
</h4>
</div>
<img
src="{{ asset('/images/imgSliderEmpty1.png') }}"
class="imgslider"
alt="Image 2"
/>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- Section de contenu à atteindre après le scroll -->
<div
class="no-margin"
id="scrollTarget"
style="width: 100%; background: #f9f9f9; padding-top: 30px"
>
<div class="no-margin" id="scrollTarget" style="width: 100%">
<!-- Header compact 2 bandes -->
<div class="header-restructured">
<!-- Ligne 1: Icône + Titre + Stats (alignés) -->
<div class="header-line-1">
<div class="header-content">
<img src="/Accueil/imagesAccueil/sejour.png" alt="Icône séjour" width="40" height="40">
<h1 class="titreDetailSej">Séjour {{ sejour.themSejour }}</h1>
<nav class="header-pills" aria-label="Statistiques du séjour">
<button id="filtre_photos_voir" class="pill stat-pill" data-filter="photos" aria-label="Voir {{ attachementsCount }} photos et vidéos partagées">
<i class="bi bi-images"></i> {{ attachementsCount }}
</button>
<button class="pill stat-pill" data-filter="audios" aria-label="Voir {{ nbmessages }} messages audio enregistrés">
<i class="bi bi-mic-fill"></i> {{ nbmessages }}
</button>
<button class="pill stat-pill" data-filter="favoris" aria-label="Voir {{ nblikes }} contenus marqués en favoris">
<i class="bi bi-heart-fill"></i> {{ nblikes }}
</button>
</nav>
</div>
</div>
<!-- Ligne 2: Dates (gris clair) -->
<div class="header-line-2">
du {{ sejour.dateDebutSejour|date("d M Y")|replace({"Jan": "janv.", "Feb": "févr.", "Mar": "mars", "Apr": "avr.", "May": "mai", "Jun": "juin", "Jul": "juil.", "Aug": "août", "Sep": "sept.", "Oct.": "oct.", "Nov.": "nov.", "Dec.": "déc."}) }}
au {{ sejour.dateFinSejour|date("d M Y")|replace({"Jan": "janv.", "Feb": "févr.", "Mar": "mars", "Apr": "avr.", "May": "mai", "Jun": "juin", "Jul": "juil.", "Aug": "août", "Sep": "sept.", "Oct.": "oct.", "Nov.": "nov.", "Dec.": "déc."}) }}
· Code : {{sejour.codeSejour}}
</div>
<!-- 🎯 MEDIA TOOLBAR CAPSULE (Senior UX) -->
<nav class="media-toolbar" aria-label="Filtres médias">
<div class="mtb-inner" role="group">
<!-- Tout -->
<button class="mtb-btn active" data-filter="all" title="Tout">
<i class="bi bi-grid-3x3-gap-fill" aria-hidden="true"></i>
<span class="mtb-count">Tout {{ attachementsCount }}</span>
</button>
<!-- Audios -->
<button class="mtb-btn" data-filter="audio" title="Audios">
<span class="icon-telephone-voicemail" >
<i class="bi bi-telephone-fill tel text-secandary" style="color:#ffd700"></i>
<i class="bi bi-voicemail vm text-secandary" style="color:#ffd700"></i>
</span>
<span class="mtb-count" data-bind="audio">0</span>
</button>
<!-- Favoris -->
<button class="mtb-btn" data-filter="favoris" title="Favoris">
<i class="bi bi-heart-fill" style="color:#f56040" aria-hidden="true"></i>
<span class="mtb-count" data-bind="fav" id="mesFavCount">{{ nblikes }}</span>
</button>
<!-- Photos -->
<button class="mtb-btn" data-filter="photos" aria-pressed="false" title="Voir toutes les photos">
<i class="bi bi-eye-fill" aria-hidden="true"></i>
</button>
</div>
</nav>
<!-- Navigation par jours -->
<div class="section-days">
<div class="date-navigation">
<div class="date-container">
{% for x, groupAttach in listeattach %}
{% set xDate = date(x) %}
{% set finDate = date(sejour.dateFinSejour) %}
{% if xDate <= finDate %}
<div
class="date-card modern-card {% if loop.last and xDate <= finDate %} active {% endif %}"
data-aos="fade-up"
data-bs-toggle="collapse"
data-day-id="{{ xDate|date('Y-m-d') }}"
data-bs-target="#demP{{ loop.index }}"
id="iconedemoP{{ loop.index }}"
role="button"
aria-label="Contenu du {{ xDate|date("d F Y")|replace({
"Jan": "janvier", "Feb": "février", "Mar": "mars", "Apr": "avril",
"May": "mai", "Jun": "juin", "Jul": "juillet", "Aug": "août",
"Sep": "septembre", "Oct": "octobre", "Nov": "novembre", "Dec": "décembre"
}) }} : {{ groupAttach.countPhotos }} photos, {{ groupAttach.countAudio }} audios, {{ groupAttach.countVideos }} vidéos"
tabindex="0"
>
<div class="card-content">
<span class="title-line">
<span class="full-date">
{{ xDate|date("D")|replace({
"Mon": "lun.","Tue": "mar.","Wed": "mer.","Thu": "jeu.","Fri": "ven.","Sat": "sam.","Sun": "dim."
}) }}
{{ xDate|date("d M")|replace({
"Jan": "janv.","Feb": "févr.","Mar": "mars","Apr": "avr.","May": "mai","Jun": "juin",
"Jul": "juil.","Aug": "août","Sep": "sept.","Oct.": "oct.","Nov.": "nov.","Dec.": "déc."
}) }}
{% if groupAttach.isFirstDay == "yes" %}
<span class="day-badge first">Début</span>
{% elseif groupAttach.isLastDay == "yes" %}
<span class="day-badge last">Dernier</span>
{% endif %}
</span>
<span class="badge-new d-none" aria-label="Nouveau contenu"></span>
</span>
<ul class="media-list-horizontal">
{% if groupAttach.countPhotos > 0 %}
<li><i class="bi bi-images"></i>{{ groupAttach.countPhotos }}</li>
{% endif %}
{% if groupAttach.countAudio > 0 %}
<li><span class="icon-telephone-voicemail">
<i class="bi bi-telephone-fill tel text-secandary" style="color:#ffd700!important"></i>
<i class="bi bi-voicemail vm text-secandary" style="color:#ffd700!important; transform: translate(-8%, -46%);font-size:0.4rem!important"></i>
</span>{{ groupAttach.countAudio }}</li>
{% endif %}
{% if groupAttach.countVideos > 0 %}
<li><i class="bi bi-camera-video-fill"></i>{{ groupAttach.countVideos }}</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Carte produit flottante -->
<div id="dynamic-card" class="dynamic-card" style="display:none">
<div id="dynamic-card-content" class="dynamic-card-content" >
<!-- Le contenu dynamique (album, pochette, montage vidéo) sera injecté ici -->
</div>
</div>
<!-- Descriptions and Attachments -->
<div class="container--gallery modern">
{% set lastValidIndex = 0 %}
{% set hasAttachments = false %}
{% for x, groupAttach in listeattach %}
{% set xDate = date(x) %}
{% set finDate = date(sejour.dateFinSejour) %}
{% if xDate <= finDate %}
{% set lastValidIndex = loop.index %}
{% set hasAttachments = true %}
<div
id="demP{{ loop.index }}"
class="collapse {% if loop.last and xDate <= finDate %}show{% endif %}"
style="padding: 2%; padding-top: 0%"
>
<div class="journal-entry">
<div class="entry-header" style="
display: none;
align-items: center;
justify-content: space-between;
background: #ffffff;
padding: 8px 15px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
border-bottom: 1px solid #eee;
flex-wrap: wrap;
">
<!-- Centre : Date + Médias -->
<div style="
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
">
<!-- Date -->
<div style="font-weight: bold; font-size: 16px; color: #333;">
{{ x|date("l d F Y")|replace({
"Monday": "Lundi", "Tuesday": "Mardi", "Wednesday": "Mercredi",
"Thursday": "Jeudi", "Friday": "Vendredi", "Saturday": "Samedi",
"Sunday": "Dimanche",
"January": "Janvier", "February": "Février", "March": "Mars",
"April": "Avril", "May": "Mai", "June": "Juin",
"July": "Juillet", "August": "Août", "September": "Septembre",
"October": "Octobre", "November": "Novembre", "December": "Décembre"
}) }}
</div>
<!-- Filtres Médias -->
<div class="filter-icons" style="display: flex; gap: 8px; flex-wrap: wrap;">
<span class="filter-badge active" data-filter="all" title="Afficher tout">
<i class="bi bi-grid-3x3-gap-fill" style="margin-right: 5px; font-size: 14px;"></i> Tout
</span>
{% if groupAttach.countPhotos > 0 %}
<span class="filter-badge" data-filter="photo" title="Filtrer les photos">
<i class="bi bi-image" style="margin-right: 5px; font-size: 14px;"></i> {{ groupAttach.countPhotos }}
</span>
{% endif %}
{% if groupAttach.countVideos > 0 %}
<span class="filter-badge" data-filter="video" title="Filtrer les vidéos">
<i class="bi bi-camera-video-fill" style="margin-right: 5px; font-size: 14px;"></i> {{ groupAttach.countVideos }}
</span>
{% endif %}
{% if groupAttach.countAudio > 0 %}
<span class="filter-badge" data-filter="audio" title="Filtrer les messages audio">
<i class="bi bi-mic-fill" style="margin-right: 5px; font-size: 14px;"></i> {{ groupAttach.countAudio }}
</span>
{% endif %}
</div>
</div>
</div>
<!-- Bouton Jour Suivant -->
<!-- Contenu -->
<div class="entry-content" id="TourContent">
<!-- Dropdown de filtrage par jour -->
<div class="day-filter-dropdown" data-day-index="{{ loop.index }}">
<button class="filter-toggle" type="button" aria-label="Options de filtrage">
<i class="bi bi-three-dots"></i>
</button>
<div class="filter-dropdown-menu">
<div class="filter-option active" data-filter="all">
<i class="bi bi-grid-3x3-gap-fill"></i>
<span>Tout</span>
<span class="count" data-count-all>0</span>
</div>
<div class="filter-option" data-filter="photo">
<i class="bi bi-image"></i>
<span>Photos</span>
<span class="count" data-count-photo>{{ groupAttach.countPhotos|default(0) }}</span>
</div>
<div class="filter-option" data-filter="video">
<i class="bi bi-camera-video-fill"></i>
<span>Vidéos</span>
<span class="count" data-count-video>{{ groupAttach.countVideos|default(0) }}</span>
</div>
<div class="filter-option" data-filter="audio">
<i class="bi bi-mic-fill"></i>
<span>Messages</span>
<span class="count" data-count-audio>{{ groupAttach.countAudio|default(0) }}</span>
</div>
</div>
</div>
<p class="description" style="margin-left:2%;width:95%;margin-top:1%;margin-bottom:1%;text-align:left">
{% for description in sejour.jourdescripdate %} {% if
description.datejourphoto|date("m/d/Y") == x|date("m/d/Y") %}
{{ description.description | nl2br }}
{% endif %} {% endfor %}
</p>
<!-- Conteneur des photos et vidéos -->
<div
class="rowimag no-margin"
style="
width: 100%;
display: flex;
flex-wrap: wrap;
margin: 0;
box-sizing: border-box;
"
>
<!-- Afficher les Photos et Vidéos -->
{% for attach in groupAttach.attachments %}
{% if attach.libiller == 'photo' %}
<div class="column" data-type="{{ attach.libiller }}">
<div class="photo-zoom photo-item">
<!-- Image sans lien -->
<img src="{{ attach.path }}" alt="{{ attach.descreption }}" loading="lazy" decoding="async" />
<!-- Icône "voir" pour ouvrir le slider -->
<div class="view-icon" onclick="viewImage('{{ attach.id_attchment }}', this)" title="Voir en plein écran">
<i class="bi bi-eye-fill"></i>
</div>
<!-- Icône du cœur avec logique existante -->
<div
class="heart-icon"
id="coeur{{ attach.id_attchment }}"
data-id="{{ attach.id_attchment }}"
data-sejour-id="{{ sejour.id }}"
data-path="{{ attach.path }}"
data-description="{{ attach.descreption }}"
>
{% if app.user %} {% if attach.is_favorite %}
<i
class="bi bi-heart-fill"
title="Sélectionnée"
style="color: #f56040"
></i>
{% else %}
<i class="bi bi-heart" title="Ajouter à ma sélection"></i>
{% endif %} {% endif %}
</div>
<div class="photo-actions" style="display: none">
<button class="menu-btn">⋮</button>
<div class="menu-options">
<button onclick="addToPack('tirage')">
🖨️ Ajouter au tirage
</button>
<button onclick="addToPack('numerique')">
💾 Ajouter au numérique
</button>
</div>
</div>
</div>
{% if attach.descreption != "" %}
<h4 class="description">{{ attach.descreption }}</h4>
{% endif %}
</div>
{% endif %}
{% if attach.libiller == 'video' %}
<div class="column" data-type="{{ attach.libiller }}">
<div class="video-container" style="position: relative; display: inline-block; width: 100%; border-radius: 8px; overflow: hidden;">
<video class="photo-zoom" controls controlslist="nodownload noplaybackrate" style="width: 100%;">
<source src="{{ attach.path }}" type="video/mp4" />
Votre navigateur ne supporte pas la lecture vidéo.
</video>
</div>
{% if attach.descreption != "" %}
<h4 class="description">{{ attach.descreption }}</h4>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
<!-- Section séparée pour les messages audio -->
{% if groupAttach.countAudio > 0 %}
<div class="audio-messages-section" style="margin-top:15px; border-top: 1px solid #eee; padding: 30px;">
<h4 style="margin-bottom: 15px; color: #555">
<i class="bi bi-mic-fill" style="margin-right: 8px; color: #ffa500"></i>
Messages vocaux ({{ groupAttach.countAudio }})
</h4>
{% if sejour.codeSejour|slice(0, 2) == 'PF' %}
{# Les séjours commençant par PF ont toujours accès #}
<div class="audio-messages-container" data-type="audio" style="display: flex; flex-wrap: wrap; gap: 15px">
{% for attach in groupAttach.attachments %}
{% if attach.libiller == 'message' %}
<div class="audio-message-item" data-type="audio" style="background: #f9f9f9; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; width: calc(33.33% - 10px); min-width: 250px; flex-grow: 1;">
<div class="audio-player-container" style="display: flex; align-items: center; margin-bottom: 10px;">
<i class="bi bi-mic-fill" style="font-size: 20px; margin-right: 10px; color: #ffa500"></i>
<audio controls controlslist="nodownload noplaybackrate" style="flex: 1">
<source src="{{ attach.path }}" type="audio/mp3" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
{% if attach.descreption != "" %}
<div class="audio-description" style="padding-left: 30px">
<p style="margin: 0; font-size: 14px; color: #555; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">
{{ attach.descreption }}
</p>
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% elseif sejour.codeSejour|slice(0, 2) == 'PP' and parentsejour.payment == 1 or sejour.codeSejour|slice(0, 2) == 'EF' and parentsejour.payment == 1 %}
{# PP ou EF avec paiement effectué #}
<div class="audio-messages-container" data-type="audio" style="display: flex; flex-wrap: wrap; gap: 15px">
{% for attach in groupAttach.attachments %}
{% if attach.libiller == 'message' %}
<div class="audio-message-item" data-type="audio" style="background: #f9f9f9; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; width: calc(33.33% - 10px); min-width: 250px; flex-grow: 1;">
<div class="audio-player-container" style="display: flex; align-items: center; margin-bottom: 10px;">
<i class="bi bi-mic-fill" style="font-size: 20px; margin-right: 10px; color: #ffa500"></i>
<audio controls controlslist="nodownload noplaybackrate" style="flex: 1">
<source src="{{ attach.path }}" type="audio/mp3" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
{% if attach.descreption != "" %}
<div class="audio-description" style="padding-left: 30px">
<p style="margin: 0; font-size: 14px; color: #555; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">
{{ attach.descreption }}
</p>
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% elseif sejour.codeSejour|slice(0, 2) == 'PP' and parentsejour.payment == 0 or sejour.codeSejour|slice(0, 2) == 'EF' and parentsejour.payment == 0 %}
{# PP ou EF sans paiement - message d'accès limité et audio désactivés #}
<div class="audio-messages-container" style="display: flex; flex-wrap: wrap; gap: 15px; opacity: 0.5; pointer-events: none; filter: grayscale(100%);">
<div class="audio-messages-restricted" data-type="audio" style="padding: 20px; background: #f0f0f0; border-radius: 8px; text-align: center; margin-bottom: 15px; width: 100%;">
<i class="bi bi-lock-fill" style="font-size: 24px; color: #808080; margin-bottom: 10px;"></i>
<p style="margin: 0; color: #555;">📢 Les messages vocaux sont disponibles via la boîte vocale premium. Un paiement est requis pour l’activer et y accéder.</p>
</div>
{% for attach in groupAttach.attachments %}
{% if attach.libiller == 'message' %}
<div class="audio-message-item" data-type="audio" style="background: #f9f9f9; border-radius: 8px; padding: 12px; display: flex; flex-direction: column; width: calc(33.33% - 10px); min-width: 250px; flex-grow: 1;">
<div class="audio-player-container" style="display: flex; align-items: center; margin-bottom: 10px;">
<i class="bi bi-mic-fill" style="font-size: 20px; margin-right: 10px; color: #ffa500"></i>
<audio controls controlslist="nodownload noplaybackrate" style="flex: 1" disabled>
<source src="{{ attach.path }}" type="audio/mp3" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
{% if attach.descreption != "" %}
<div class="audio-description" style="padding-left: 30px">
<p style="margin: 0; font-size: 14px; color: #555; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">
{{ attach.descreption }}
</p>
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% if not hasAttachments %}
<div class="welcome-message" style="padding: 50px 20px; text-align: center; background: #f9f9f9; border-radius: 15px; margin: 30px auto; max-width: 800px; box-shadow: 0 5px 15px rgba(0,0,0,0.05);">
<img src="/images/welcome-icon.svg" alt="Bienvenue" style="width: 80px; margin-bottom: 20px;" loading="lazy" decoding="async" fetchpriority="low" onerror="this.src='/images/Accompagnateur/Picto5sur5.svg'; this.style.width='120px';">
<h2 style="color: #41a2aa; margin-bottom: 20px; font-size: 24px;">Bienvenue sur votre espace séjour !</h2>
<p style="font-size: 18px; color: #555; margin-bottom: 15px;">
Aucun contenu n'a encore été partagé pour ce séjour.
</p>
<p style="font-size: 16px; color: #666; margin-bottom: 25px;">
L'accompagnateur partagera bientôt des photos, vidéos et messages vocaux.
<br>Revenez consulter cette page régulièrement pour suivre les aventures du séjour !
</p>
<div style="display: flex; justify-content: center; gap: 15px; flex-wrap: wrap;">
<div style="display: flex; align-items: center; background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<i class="bi bi-images" style="font-size: 24px; color: #3a8d95; margin-right: 10px;"></i>
<span>Photos du séjour</span>
</div>
<div style="display: flex; align-items: center; background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<i class="bi bi-camera-video-fill" style="font-size: 24px; color: #41a2aa; margin-right: 10px;"></i>
<span>Vidéos des activités</span>
</div>
<div style="display: flex; align-items: center; background: #fff; padding: 15px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<i class="bi bi-mic-fill" style="font-size: 24px; color: #ffa500; margin-right: 10px;"></i>
<span>Messages vocaux</span>
</div>
</div>
</div>
<!-- Espace supplémentaire pour déplacer le footer vers le bas -->
<div style="height: 300px;"></div>
{% endif %}
<!-- JavaScript pour les pills et la gestion du nouveau contenu -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Valider les liens de produits au chargement
setTimeout(() => {
if (typeof window.validateProductLinks === 'function') {
window.validateProductLinks();
}
}, 1000);
// ⚡ OPTIMISATION: Event delegation avec throttling pour éviter les clics multiples
let clickThrottleTimeout = null;
document.addEventListener('click', function(e) {
// Throttling pour éviter les clics multiples rapides
if (clickThrottleTimeout) return;
const pill = e.target.closest('.stat-pill');
if (pill) {
clickThrottleTimeout = setTimeout(() => {
clickThrottleTimeout = null;
}, 100);
// ⚡ OPTIMISÉ: Une seule boucle pour gérer les états actifs
const statPills = document.querySelectorAll('.stat-pill');
for (let i = 0; i < statPills.length; i++) {
const p = statPills[i];
if (p && p.classList) {
if (p === pill) {
p.classList.add('active');
} else {
p.classList.remove('active');
}
}
}
// Déclencher le filtrage du contenu
const filter = pill.getAttribute('data-filter');
if (typeof window.filterContent === 'function') {
window.filterContent(filter);
}
}
});
// Fonction de filtrage du contenu - globale
window.filterContent = function(filter) {
// ⚡ OPTIMISÉ: Suppression des console.log en production
if (window.location.hostname === 'localhost' || window.location.hostname.includes('dev')) {
console.log('Filtrage par:', filter);
}
const dateNavigation = document.querySelector('.date-navigation');
const sectionDays = document.querySelector('.section-days');
if (filter === 'favoris') {
// Afficher la vue des favoris
showFavoritesView();
} else if (filter === 'audio') {
// Afficher la vue des messages audio
showAudiosView();
} else if (filter === 'photos') {
// Ouvrir le carrousel du jour actif ou du dernier jour
openCurrentDayCarousel();
} else {
// Restaurer la vue normale des jours
showDaysView();
}
}
// Fonction pour afficher la vue des favoris - globale
window.showFavoritesView = function() {
const sectionDays = document.querySelector('.section-days');
const containerGallery = document.querySelector('.container--gallery');
// Masquer le container gallery
if (containerGallery) {
containerGallery.style.display = 'none';
}
// Mettre à jour l'état des boutons
updatePhotosButtonState('favorites');
updateAudioButtonState('favorites');
updateFavoritesButtonState('favorites');
const favoriteCount = getCurrentFavoriteCount();
// sla vue des favoris avec e-commerce
const favoritesHTML = `
<div class="favorites-ecommerce-view">
<!-- Header avec progression -->
<div class="favorites-header">
<div class="favorites-title">
<h3>Vos souvenirs sélectionnés</h3>
<span class="favorites-count" id="favorites-count">${favoriteCount} photos</span>
</div>
<button class="btn-back-to-days" onclick="window.showDaysView()">
<i class="bi bi-arrow-left"></i>
Retour aux jours
</button>
</div>
<!-- Grille des favoris -->
<div class="favorites-content">
<div class="favorites-grid" id="favoritesGrid">
${generateFavoritesGrid()}
</div>
<!-- Propositions de produits -->
<div class="product-suggestions">
${generateProductSuggestions(favoriteCount)}
</div>
</div>
</div>
`;
sectionDays.innerHTML = favoritesHTML;
}
// Fonction pour afficher la vue des messages audio - globale
window.showAudiosView = function() {
const sectionDays = document.querySelector('.section-days');
const containerGallery = document.querySelector('.container--gallery');
// Masquer le container gallery
if (containerGallery) {
containerGallery.style.display = 'none';
}
// Mettre à jour l'état des boutons
updatePhotosButtonState('audios');
updateAudioButtonState('audios');
updateFavoritesButtonState('audios');
// Collecter tous les messages audio de tous les jours
const allAudioMessages = [];
const audioItems = document.querySelectorAll('.audio-message-item[data-type="audio"]');
audioItems.forEach(item => {
const audio = item.querySelector('audio source');
const description = item.querySelector('.audio-description p');
const dayContainer = item.closest('[id^="demP"]');
let dayTitle = 'Jour inconnu';
if (dayContainer) {
// Trouver le titre du jour
const dayIndex = dayContainer.id.replace('demP', '');
const dayCard = document.querySelector(`[data-target="#demP${dayIndex}"]`);
if (dayCard) {
dayTitle = dayCard.textContent.trim();
} else {
// Essayer de trouver dans les cartes de date
const dateCard = document.querySelector(`.date-card[data-bs-target="#demP${dayIndex}"], .date-card[data-target="#demP${dayIndex}"]`);
if (dateCard) {
const dateText = dateCard.querySelector('.date-text, .card-title, h5, .title-line');
if (dateText) {
dayTitle = dateText.textContent.trim();
}
}
}
}
if (audio) {
allAudioMessages.push({
src: audio.src,
description: description ? description.textContent.trim() : '',
day: dayTitle,
isRestricted: item.closest('.audio-messages-container[style*="opacity: 0.5"]') !== null
});
}
});
// Créer la vue des messages audio
const audiosHTML = `
<div class="audios-view">
<!-- Header -->
<div class="audios-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 20px; background: #f8f9fa; border-radius: 10px;">
<div class="audios-title">
<h3><span class="icon-telephone-voicemail">
<i class="bi bi-telephone-fill tel" style="color: #ffd700;font-size:1.5em"></i>
<i class="bi bi-voicemail vm" style="color: #ffd700;font-size: 0.9rem;
transform: translate(-40%, -46%);"></i>
</span>
<span class="audios-count">${allAudioMessages.length} messages</span>
</div>
<button class="btn-back-to-days" onclick="window.showDaysView()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer;">
<i class="bi bi-arrow-left"></i>
Retour aux jours
</button>
</div>
<!-- Grille des messages audio -->
<div class="audios-content" style="display: flex; flex-wrap: wrap; gap: 20px;">
${allAudioMessages.map(msg => `
<div class="audio-message-card" style="background: #f9f9f9; border-radius: 12px; padding: 20px; width: calc(50% - 10px); min-width: 300px; ${msg.isRestricted ? 'opacity: 0.5; filter: grayscale(100%);' : ''}">
${msg.day && msg.day !== 'Jour inconnu' ? `
<div class="audio-day-tag" style="background: #e9ecef; color: #495057; padding: 4px 8px; border-radius: 15px; font-size: 12px; margin-bottom: 12px; display: inline-block;">
${msg.day}
</div>
` : ''}
<div class="audio-player-container" style="display: flex; align-items: center; margin-bottom: 12px;">
<i class="bi bi-mic-fill" style="font-size: 24px; margin-right: 15px; color: #ffa500;"></i>
<audio controls controlslist="nodownload noplaybackrate" style="flex: 1; border-radius: 5px;" ${msg.isRestricted ? 'disabled' : ''}>
<source src="${msg.src}" type="audio/mp3" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
${msg.description ? `
<div class="audio-description" style="padding-left: 39px;">
<p style="margin: 0; font-size: 14px; color: #666; line-height: 1.4;">${msg.description}</p>
</div>
` : ''}
${msg.isRestricted ? `
<div class="audio-restricted-notice" style="text-align: center; margin-top: 10px; padding: 8px; background: #fff3cd; border-radius: 5px; border: 1px solid #ffeaa7;">
<i class="bi bi-lock-fill" style="color: #856404; margin-right: 5px;"></i>
<small style="color: #856404;">Accès premium requis</small>
</div>
` : ''}
</div>
`).join('')}
</div>
${allAudioMessages.length === 0 ? `
<div class="no-audios" style="text-align: center; padding: 60px 20px; color: #6c757d;">
<i class="bi bi-mic" style="font-size: 4rem; margin-bottom: 20px; opacity: 0.3;"></i>
<h4>Aucun message vocal</h4>
<p>Il n'y a pas encore de messages vocaux dans ce séjour.</p>
</div>
` : ''}
</div>
`;
sectionDays.innerHTML = audiosHTML;
}
// Variables pour sauvegarder le contenu original
let originalDaysContent = null;
// Sauvegarder le contenu original au chargement
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
const containerGallery = document.querySelector('.container--gallery');
if (containerGallery) {
originalDaysContent = containerGallery.outerHTML;
}
}, 1000);
});
// Fonction pour ouvrir le carrousel contextuel - globale
window.openCurrentDayCarousel = function() {
// Détecter la vue active
const currentView = detectCurrentView();
switch (currentView) {
case 'favorites':
openFavoritesCarousel();
break;
case 'audios':
// Dans la vue audios, pas de carrousel photos pertinent
console.log('Carrousel photos non disponible dans la vue audios');
return;
case 'days':
default:
openActiveDayCarousel();
break;
}
}
// Fonction pour mettre à jour l'état du bouton photos selon le contexte
function updatePhotosButtonState(currentView) {
const photosBtn = document.querySelector('.mtb-btn[data-filter="photos"]');
if (!photosBtn) return;
const btnIcon = photosBtn.querySelector('i');
const btnCount = photosBtn.querySelector('.mtb-count');
switch (currentView) {
case 'favorites':
// Dans les favoris : bouton actif, ouvre le carrousel des favoris
photosBtn.style.opacity = '1';
photosBtn.style.pointerEvents = 'auto';
photosBtn.title = 'Voir le carrousel des favoris';
if (btnIcon) btnIcon.style.color = '#007bff';
if (btnCount) btnCount.style.display = 'none'; // Cacher le compteur
break;
case 'audios':
// Dans les audios : bouton désactivé
photosBtn.style.opacity = '0.4';
photosBtn.style.pointerEvents = 'none';
photosBtn.title = 'Carrousel non disponible dans la vue audios';
if (btnIcon) btnIcon.style.color = '#6c757d';
if (btnCount) btnCount.style.display = 'none';
break;
case 'days':
default:
// Vue normale : utiliser la logique existante du carrousel
photosBtn.style.opacity = '1';
photosBtn.style.pointerEvents = 'auto';
photosBtn.title = 'Voir les photos du jour actif';
if (btnIcon) btnIcon.style.color = '';
if (btnCount) btnCount.style.display = 'none'; // Cacher le compteur pour éviter la confusion
break;
}
}
// Fonction pour mettre à jour l'état du bouton audio selon le contexte
window.updateAudioButtonState = function updateAudioButtonState(currentView) {
const audioBtn = document.querySelector('.mtb-btn[data-filter="audio"]');
if (!audioBtn) return;
const btnCount = audioBtn.querySelector('.mtb-count');
switch (currentView) {
case 'favorites':
// Dans les favoris : pas d'action sur le compteur audio
break;
case 'audios':
// Dans les audios : afficher le nombre total calculé par le frontend
const totalAudioCount = document.querySelectorAll('.audio-message-item[data-type="audio"]').length;
if (btnCount) {
btnCount.textContent = totalAudioCount;
}
break;
case 'days':
default:
// Vue normale : compter les audios du jour actif
const activeDayAudioCount = countActiveDayAudios();
if (btnCount) {
btnCount.textContent = activeDayAudioCount;
}
break;
}
}
// Fonction pour mettre à jour l'état du bouton favoris selon le contexte
window.updateFavoritesButtonState = function updateFavoritesButtonState(currentView) {
const favoritesBtn = document.querySelector('.mtb-btn[data-filter="favoris"]');
if (!favoritesBtn) return;
const btnIcon = favoritesBtn.querySelector('i');
const btnCount = favoritesBtn.querySelector('.mtb-count');
switch (currentView) {
case 'favorites':
// Dans les favoris : bouton actif
favoritesBtn.style.opacity = '1';
favoritesBtn.style.pointerEvents = 'auto';
favoritesBtn.title = 'Vous êtes dans la vue favoris';
if (btnIcon) btnIcon.style.color = '#f56040';
if (btnCount) btnCount.style.display = 'inline';
favoritesBtn.classList.add('active');
break;
case 'audios':
// Dans les audios : bouton désactivé
favoritesBtn.style.opacity = '0.4';
favoritesBtn.style.pointerEvents = 'none';
favoritesBtn.title = 'Favoris non disponibles dans la vue audios';
if (btnIcon) btnIcon.style.color = '#6c757d';
if (btnCount) btnCount.style.display = 'none';
favoritesBtn.classList.remove('active');
break;
case 'days':
default:
// Vue normale : bouton normal
favoritesBtn.style.opacity = '1';
favoritesBtn.style.pointerEvents = 'auto';
favoritesBtn.title = 'Voir les favoris';
if (btnIcon) btnIcon.style.color = '#f56040';
if (btnCount) btnCount.style.display = 'inline';
favoritesBtn.classList.remove('active');
break;
}
}
// Fonction pour compter les audios du jour actif (réutilise la même logique)
// ⚡ OPTIMISÉ: Cache des éléments DOM pour éviter les requêtes répétitives
let cachedActiveDay = null;
let cachedAudioCount = 0;
let lastCacheTime = 0;
const CACHE_DURATION = 1000; // 1 seconde
function countActiveDayAudios() {
const now = Date.now();
// Utiliser le cache si récent
if (cachedActiveDay && (now - lastCacheTime) < CACHE_DURATION) {
return cachedAudioCount;
}
// Trouver le jour actuellement ouvert
let activeDay = document.querySelector('.collapse.show[id^="demP"]');
// Si aucun jour ouvert, prendre le dernier jour
if (!activeDay) {
const allDays = document.querySelectorAll('.collapse[id^="demP"]');
activeDay = allDays[allDays.length - 1];
}
if (!activeDay) {
cachedActiveDay = null;
cachedAudioCount = 0;
lastCacheTime = now;
return 0;
}
// Compter les messages audio dans ce jour
const audiosInDay = activeDay.querySelectorAll('.audio-message-item[data-type="audio"]');
const count = audiosInDay.length;
// Mettre à jour le cache
cachedActiveDay = activeDay;
cachedAudioCount = count;
lastCacheTime = now;
return count;
}
// Fonction pour détecter la vue active
// ⚡ OPTIMISÉ: Cache de la vue active pour éviter les requêtes DOM répétitives
let cachedCurrentView = null;
let lastViewCheck = 0;
const VIEW_CACHE_DURATION = 500; // 500ms
function detectCurrentView() {
const now = Date.now();
// Utiliser le cache si récent
if (cachedCurrentView && (now - lastViewCheck) < VIEW_CACHE_DURATION) {
return cachedCurrentView;
}
let view = 'days';
if (document.querySelector('.favorites-ecommerce-view')) {
view = 'favorites';
} else if (document.querySelector('.audios-view')) {
view = 'audios';
}
// Mettre à jour le cache
cachedCurrentView = view;
lastViewCheck = now;
return view;
}
// ⚡ OPTIMISÉ: Cache global des éléments DOM fréquemment utilisés
const domCache = {
favoritesGrid: null,
lastFavoritesUpdate: 0,
CACHE_DURATION: 2000 // 2 secondes
};
function getCachedFavorites() {
const now = Date.now();
if (!domCache.favoritesGrid || (now - domCache.lastFavoritesUpdate) > domCache.CACHE_DURATION) {
domCache.favoritesGrid = document.querySelector('.favorites-grid');
domCache.lastFavoritesUpdate = now;
}
return domCache.favoritesGrid;
}
// Fonction pour ouvrir le carrousel des favoris
function openFavoritesCarousel() {
const favoritesGrid = getCachedFavorites();
const favoriteItems = favoritesGrid ? favoritesGrid.querySelectorAll('.favorite-item') : [];
if (favoriteItems.length > 0) {
// Trouver le premier favori avec une action de vue
const firstFavorite = favoriteItems[0];
const viewBtn = firstFavorite.querySelector('.btn-view-favorite');
if (viewBtn && viewBtn.onclick) {
viewBtn.click();
} else {
// Fallback : extraire l'ID du data-id et utiliser viewImage
const favoriteId = firstFavorite.dataset.id;
if (favoriteId && typeof window.viewImage === 'function') {
window.viewImage(favoriteId, firstFavorite);
}
}
} else {
console.log('Aucun favori trouvé pour ouvrir le carrousel');
}
}
// Fonction pour ouvrir le carrousel du jour actif
function openActiveDayCarousel() {
// Trouver le jour actuellement ouvert (avec classe 'show')
let activeDay = document.querySelector('.collapse.show[id^="demP"]');
// Si aucun jour n'est ouvert, prendre le dernier jour
if (!activeDay) {
const allDays = document.querySelectorAll('.collapse[id^="demP"]');
activeDay = allDays[allDays.length - 1]; // Dernier jour
}
if (!activeDay) {
console.warn('Aucun jour trouvé pour ouvrir le carrousel');
return;
}
// Trouver la première photo de ce jour
const viewIcon = activeDay.querySelector('.view-icon[onclick*="viewImage"]');
if (viewIcon) {
// Extraire l'ID de la photo du onclick
const onclickAttr = viewIcon.getAttribute('onclick');
const match = onclickAttr.match(/viewImage\('(\d+)'/);
if (match) {
const imageId = match[1];
// Ouvrir le slider avec cette image
if (typeof window.viewImage === 'function') {
window.viewImage(imageId, viewIcon);
}
}
} else {
// Fallback : chercher dans tous les jours
const allDays = document.querySelectorAll('.collapse[id^="demP"]');
for (let day of allDays) {
const photoInDay = day.querySelector('.view-icon[onclick*="viewImage"]');
if (photoInDay) {
photoInDay.click();
break;
}
}
}
}
// Fonction pour afficher la vue normale des jours - globale
window.showDaysView = function() {
const sectionDays = document.querySelector('.section-days');
const containerGallery = document.querySelector('.container--gallery');
// Si la section days contient des vues spéciales (favoris/audios), les supprimer
if (sectionDays && (sectionDays.innerHTML.includes('favorites-ecommerce-view') || sectionDays.innerHTML.includes('audios-view'))) {
// Restaurer le contenu original si disponible
if (originalDaysContent) {
sectionDays.innerHTML = originalDaysContent;
} else {
// Fallback : recharger la page si pas de sauvegarde
location.reload();
return;
}
}
// Restaurer la visibilité du container gallery
if (containerGallery) {
containerGallery.style.display = 'block';
}
// Appeler le filtre "all" pour restaurer l'affichage complet
if (typeof window.filterContent === 'function') {
window.filterContent('toutVoir');
}
// Mettre à jour l'état des boutons
updatePhotosButtonState('days');
updateAudioButtonState('days');
updateFavoritesButtonState('days');
}
// Fonction pour générer les cartes de jours (alternative simple)
function generateDaysCards() {
// Cette fonction pourrait régénérer les cartes, mais pour simplifier
// on recharge la page dans showDaysView()
return '';
}
// Fonction pour revenir à la vue des jours
// Fonction pour générer la grille des favoris
function generateFavoritesGrid() {
const favorites = getFavoriteItems();
if (favorites.length === 0) {
return `
<div class="no-favorites">
<i class="bi bi-heart" style="font-size: 3rem; color: #ccc; margin-bottom: 1rem;"></i>
<h4>Aucun favori sélectionné</h4>
<p>Cliquez sur <i class="bi bi-heart" style="color: #e91e63;"></i> sur vos photos préférées pour les ajouter ici.</p>
</div>
`;
}
return favorites.map(item => `
<div class="favorite-item" data-id="${item.id}">
${item.type === 'image' ?
`<img src="${item.url}" alt="Photo favorite" loading="lazy">` :
item.type === 'video' ?
`<video src="${item.url}" poster="${item.thumbnail}"></video>
<div class="video-overlay"><i class="bi bi-play-circle"></i></div>` :
`<div class="audio-item">
<i class="bi bi-mic-fill"></i>
<span>Audio ${item.duration || '00:00'}</span>
</div>`
}
<!-- Actions overlay -->
<div class="favorite-actions">
<button class="btn-view-favorite" onclick="viewFavorite('${item.id}')" title="Voir en grand">
<i class="bi bi-eye"></i>
<span>Voir</span>
</button>
<button class="btn-remove-favorite" onclick="removeFavorite('${item.id}')" title="Retirer des favoris">
<i class="bi bi-heart-fill"></i>
<span>Retirer</span>
</button>
</div>
</div>
`).join('');
}
// Fonction pour réorganiser la grille des favoris
function reorganizeFavoritesGrid() {
const favoritesGrid = document.getElementById('favoritesGrid');
if (!favoritesGrid) return;
const remainingItems = favoritesGrid.querySelectorAll('.favorite-item');
if (remainingItems.length === 0) {
// Afficher le message "Aucun favori"
favoritesGrid.innerHTML = `
<div class="no-favorites">
<i class="bi bi-heart" style="font-size: 3rem; color: #ccc; margin-bottom: 1rem;"></i>
<h4>Aucun favori sélectionné</h4>
<p>Cliquez sur <i class="bi bi-heart" style="color: #e91e63;"></i> sur vos photos préférées pour les ajouter ici.</p>
</div>
`;
} else {
// Réorganiser les éléments restants avec une animation douce
remainingItems.forEach((item, index) => {
item.style.transition = 'all 0.3s ease';
item.style.order = index;
});
}
}
// Fonction pour récupérer les éléments favoris
function getFavoriteItems() {
const favorites = [];
// ⚡ OPTIMISÉ: Cache et parcours plus efficace des favoris
const heartIcons = document.querySelectorAll('.heart-icon');
for (let i = 0; i < heartIcons.length; i++) {
const heartIcon = heartIcons[i];
const heartFill = heartIcon.querySelector('.bi-heart-fill');
if (heartFill) {
const id = heartIcon.getAttribute('data-id');
const path = heartIcon.getAttribute('data-path');
const description = heartIcon.getAttribute('data-description');
if (id && path) {
// Déterminer le type de média
let type = 'image';
let url = path;
let thumbnail = path;
if (path.includes('.mp4') || path.includes('.mov') || path.includes('.avi')) {
type = 'video';
thumbnail = path.replace(/\.(mp4|mov|avi)$/i, '_thumb.jpg');
} else if (path.includes('.mp3') || path.includes('.wav') || path.includes('.m4a')) {
type = 'audio';
url = path;
}
// Récupérer la date depuis le conteneur parent
const dateCard = heartIcon.closest('[id^="demP"]');
let date = 'Date inconnue';
if (dateCard) {
const dateElement = dateCard.querySelector('.full-date, .day-title');
if (dateElement) {
date = dateElement.textContent.trim();
}
}
favorites.push({
id: id,
url: url,
thumbnail: thumbnail,
type: type,
date: date,
description: description || ''
});
}
}
}
// ⚡ OPTIMISÉ: Log conditionnel pour réduire l'impact performance
if (favorites.length > 0) {
console.log('Favoris trouvés:', favorites.length);
}
return favorites;
}
// Rendre les fonctions accessibles globalement
window.viewFavorite = viewFavorite;
window.removeFavorite = removeFavorite;
window.getFavoriteItems = getFavoriteItems;
// Fonction pour supprimer un favori
function removeFavorite(id) {
const heartIcon = document.querySelector(`[data-id="${id}"]`);
if (heartIcon) {
const heartFill = heartIcon.querySelector('.bi-heart-fill');
if (heartFill && heartFill.classList) {
heartFill.classList.remove('bi-heart-fill');
heartFill.classList.add('bi-heart');
heartFill.style.color = '';
}
}
const favoriteItem = document.querySelector(`.favorite-item[data-id="${id}"]`);
if (favoriteItem) {
// Animation de disparition puis suppression complète
favoriteItem.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
favoriteItem.style.opacity = '0';
favoriteItem.style.transform = 'scale(0.8)';
setTimeout(() => {
favoriteItem.remove();
// Appeler la fonction métier si présente
const heartIcon = document.querySelector(`#coeur${id}`);
const sejourId = heartIcon && heartIcon.dataset.sejourId ? heartIcon.dataset.sejourId : '';
supprimerFavoris(id, sejourId);
// Mettre à jour les compteurs
updateAllFavoriteCounters();
// Réorganiser la grille si nécessaire
reorganizeFavoritesGrid();
}, 300);
}
}
// Slider plein écran pour les favoris
class FavoritesSlider {
constructor() {
this.currentIndex = 0;
this.favorites = [];
this.isOpen = false;
this.touchStartX = 0;
this.touchEndX = 0;
}
open(favoriteId) {
console.log('Ouverture du slider pour:', favoriteId);
// Récupérer tous les favoris images
this.favorites = getFavoriteItems().filter(item => item.type === 'image');
if (this.favorites.length === 0) {
console.error('Aucun favori image trouvé');
return;
}
// Trouver l'index de l'image courante
this.currentIndex = this.favorites.findIndex(fav => fav.id === favoriteId);
if (this.currentIndex === -1) this.currentIndex = 0;
console.log('Index courant:', this.currentIndex, 'Total:', this.favorites.length);
this.createSlider();
this.showSlide(this.currentIndex);
this.attachEvents();
this.isOpen = true;
// Animation d'ouverture immédiate pour favoris avec diagnostic
setTimeout(() => {
if (window.diagnoseSliderDisplay) {
window.diagnoseSliderDisplay('#favoritesSlider');
}
}, 50);
}
createSlider() {
// ⚡ OPTIMISATION: Créer le slider seulement quand nécessaire
if (document.querySelector('#favoritesSlider')) {
return; // Déjà créé, ne pas recréer
}
// Structure HTML minimale et optimisée
const sliderHTML = `
<div class="favorites-slider" id="favoritesSlider" style="
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0,0,0,0.95) !important;
z-index: 9999 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
opacity: 1 !important;
visibility: visible !important;
">
<div class="slider-overlay"></div>
<!-- Header avec contrôles -->
<div class="slider-header">
<div class="slider-controls">
<button class="slider-btn favorite-btn" title="Retirer des favoris">
<i class="bi bi-heart-fill"></i>
</button>
<button class="slider-btn zoom-btn" title="Zoom">
<i class="bi bi-zoom-in"></i>
</button>
<button class="slider-btn close-btn" title="Fermer">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<!-- Navigation -->
<button class="slider-nav prev-btn" title="Précédent">
<i class="bi bi-arrow-left-circle-fill"></i>
</button>
<button class="slider-nav next-btn" title="Suivant">
<i class="bi bi-arrow-right-circle-fill"></i>
</button>
<!-- Container des slides -->
<div class="slider-container">
<div class="slider-track" id="sliderTrack">
${this.favorites.map((fav, index) => `
<div class="slide" data-index="${index}">
<div class="slide-content">
<img src="${fav.url}" alt="${fav.description || 'Photo favorite'}"
loading="${index <= 2 ? 'eager' : 'lazy'}"
draggable="false">
</div>
<div class="slide-info">
<h4>${fav.description || 'Photo favorite'}</h4>
<p>${fav.date || 'Date inconnue'}</p>
</div>
</div>
`).join('')}
</div>
</div>
<!-- Thumbnails -->
<div class="slider-thumbnails">
${this.favorites.map((fav, index) => `
<div class="thumbnail ${index === this.currentIndex ? 'active' : ''}"
data-index="${index}">
<img src="${fav.url}" alt="Thumbnail ${index + 1}">
</div>
`).join('')}
</div>
</div>
`;
// Injecter dans le DOM
document.body.insertAdjacentHTML('beforeend', sliderHTML);
}
showSlide(index) {
if (index < 0 || index >= this.favorites.length) return;
this.currentIndex = index;
const track = document.getElementById('sliderTrack');
const translateX = -index * 100;
track.style.transform = `translateX(${translateX}%)`;
// ⚡ OPTIMISÉ: Éviter forEach sur querySelectorAll (mémoire)
const thumbnails = document.querySelectorAll('.thumbnail');
for (let i = 0; i < thumbnails.length; i++) {
const thumb = thumbnails[i];
if (thumb?.classList) {
thumb.classList.toggle('active', i === index);
}
}
// Mettre à jour le bouton favori
const currentFav = this.favorites[index];
const favoriteBtn = document.querySelector('.slider-controls .favorite-btn');
if (favoriteBtn && currentFav) {
favoriteBtn.setAttribute('data-id', currentFav.id);
}
}
nextSlide() {
const nextIndex = (this.currentIndex + 1) % this.favorites.length;
this.showSlide(nextIndex);
}
prevSlide() {
const prevIndex = (this.currentIndex - 1 + this.favorites.length) % this.favorites.length;
this.showSlide(prevIndex);
}
close() {
if (!this.isOpen) return;
const slider = document.querySelector('.favorites-slider');
if (slider && slider.classList) {
slider.classList.remove('active');
setTimeout(() => {
slider.remove();
this.isOpen = false;
}, 300);
}
}
attachEvents() {
const slider = document.getElementById('favoritesSlider');
// Bouton fermer
slider.querySelector('.close-btn').addEventListener('click', () => this.close());
// Navigation
slider.querySelector('.prev-btn').addEventListener('click', () => this.prevSlide());
slider.querySelector('.next-btn').addEventListener('click', () => this.nextSlide());
// ⚡ OPTIMISÉ: Event delegation au lieu de listeners multiples
const thumbContainer = slider.querySelector('.thumbnails-container') || slider;
if (thumbContainer && !thumbContainer.hasAttribute('data-thumb-delegated')) {
thumbContainer.addEventListener('click', (e) => {
const thumb = e.target.closest('.thumbnail');
if (thumb) {
const thumbnails = slider.querySelectorAll('.thumbnail');
const index = Array.from(thumbnails).indexOf(thumb);
if (index !== -1) this.showSlide(index);
}
});
thumbContainer.setAttribute('data-thumb-delegated', 'true');
}
// Bouton favori
slider.querySelector('.favorite-btn').addEventListener('click', (e) => {
const favoriteId = e.currentTarget.getAttribute('data-id');
if (favoriteId) {
removeFavorite(favoriteId);
// Recharger le slider avec les favoris mis à jour
setTimeout(() => {
this.close();
if (getFavoriteItems().filter(item => item.type === 'image').length > 0) {
this.open(this.favorites[0]?.id);
}
}, 100);
}
});
// Clavier
document.addEventListener('keydown', this.handleKeyboard.bind(this));
// Clic sur overlay pour fermer
slider.querySelector('.slider-overlay').addEventListener('click', () => this.close());
// Touch/swipe sur mobile
const track = slider.querySelector('.slider-track');
track.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true });
track.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: true });
// Zoom sur clic simple (5 niveaux) + double-clic (legacy)
slider.querySelectorAll('.slide img').forEach(img => {
let zoomLevel = 1;
let isDragging = false;
let startX, startY, translateX = 0, translateY = 0;
// Zoom sur clic simple (nouveau système 5 niveaux)
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Cycle à travers 5 niveaux de zoom
switch(zoomLevel) {
case 1: zoomLevel = 1.5; break; // 1x → 1.5x
case 1.5: zoomLevel = 2; break; // 1.5x → 2x
case 2: zoomLevel = 3; break; // 2x → 3x
case 3: zoomLevel = 4; break; // 3x → 4x
case 4: zoomLevel = 5; break; // 4x → 5x
case 5:
zoomLevel = 1; // 5x → 1x (reset)
translateX = 0;
translateY = 0;
break;
}
console.log('FavoritesSlider - Zoom niveau:', zoomLevel);
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
// Curseur selon le niveau
if (zoomLevel === 1) {
img.style.cursor = 'zoom-in';
img.removeAttribute('data-zoomed');
} else if (zoomLevel === 5) {
img.style.cursor = 'zoom-out';
img.setAttribute('data-zoomed', zoomLevel);
} else {
img.style.cursor = 'zoom-in';
img.setAttribute('data-zoomed', zoomLevel);
}
});
// Drag pour déplacer l'image zoomée
img.addEventListener('mousedown', (e) => {
if (zoomLevel > 1) {
isDragging = true;
startX = e.clientX - translateX;
startY = e.clientY - translateY;
img.style.cursor = 'grabbing';
e.preventDefault();
e.stopPropagation();
}
});
const handleMouseMove = (e) => {
if (isDragging && zoomLevel > 1) {
translateX = e.clientX - startX;
translateY = e.clientY - startY;
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
}
};
const handleMouseUp = () => {
if (isDragging) {
isDragging = false;
if (zoomLevel === 1) {
img.style.cursor = 'zoom-in';
} else if (zoomLevel === 5) {
img.style.cursor = 'zoom-out';
} else {
img.style.cursor = 'zoom-in';
}
}
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
// Support tactile pour mobile
img.addEventListener('touchstart', (e) => {
if (zoomLevel > 1 && e.touches.length === 1) {
isDragging = true;
const touch = e.touches[0];
startX = touch.clientX - translateX;
startY = touch.clientY - translateY;
e.preventDefault();
}
});
img.addEventListener('touchmove', (e) => {
if (isDragging && zoomLevel > 1 && e.touches.length === 1) {
const touch = e.touches[0];
translateX = touch.clientX - startX;
translateY = touch.clientY - startY;
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
e.preventDefault();
}
});
img.addEventListener('touchend', () => {
isDragging = false;
});
// Initialiser le curseur
img.style.cursor = 'zoom-in';
// Garder aussi le double-clic pour compatibilité (legacy)
img.addEventListener('dblclick', this.handleZoom.bind(this));
});
}
handleKeyboard(e) {
if (!this.isOpen) return;
switch(e.key) {
case 'Escape':
this.close();
break;
case 'ArrowLeft':
e.preventDefault();
this.prevSlide();
break;
case 'ArrowRight':
e.preventDefault();
this.nextSlide();
break;
}
}
handleTouchStart(e) {
this.touchStartX = e.changedTouches[0].screenX;
}
handleTouchEnd(e) {
this.touchEndX = e.changedTouches[0].screenX;
this.handleSwipe();
}
handleSwipe() {
const swipeThreshold = 50;
const diff = this.touchStartX - this.touchEndX;
if (Math.abs(diff) > swipeThreshold) {
if (diff > 0) {
this.nextSlide(); // Swipe left -> next
} else {
this.prevSlide(); // Swipe right -> prev
}
}
}
handleZoom(e) {
const img = e.target;
// Vérifier que l'élément et ses propriétés existent
if (!img || !img.classList) {
console.warn('Élément image invalide pour le zoom');
return;
}
// Gérer les différents niveaux de zoom
if (img.classList.contains('zoom-4x')) {
// Retour à la taille normale
img.classList.remove('zoom-4x', 'zoom-3x', 'zoom-2x', 'zoomed');
img.style.cursor = 'pointer';
img.style.transform = 'scale(1)'; // Réinitialiser la position
} else if (img.classList.contains('zoom-3x')) {
// Passer au zoom 4x
img.classList.remove('zoom-3x');
img.classList.add('zoom-4x');
img.style.cursor = 'zoom-out';
img.style.transform = 'scale(4)'; // Réinitialiser la position
} else if (img.classList.contains('zoom-2x')) {
// Passer au zoom 3x
img.classList.remove('zoom-2x');
img.classList.add('zoom-3x');
img.style.cursor = 'zoom-in';
img.style.transform = 'scale(3)'; // Réinitialiser la position
} else if (img.classList.contains('zoomed')) {
// Passer au zoom 2x
img.classList.remove('zoomed');
img.classList.add('zoom-2x');
img.style.cursor = 'zoom-in';
img.style.transform = 'scale(2)'; // Réinitialiser la position
} else {
// Premier zoom (1.5x)
img.classList.add('zoomed');
img.style.cursor = 'zoom-in';
img.style.transform = 'scale(1.5)'; // Réinitialiser la position
}
}
}
// Slider universel pour toutes les images (favoris + galerie normale)
class UniversalImageSlider {
constructor() {
this.currentIndex = 0;
this.images = [];
this.isOpen = false;
this.touchStartX = 0;
this.touchEndX = 0;
}
// Ouvrir avec une image spécifique depuis les favoris
openFromFavorites(favoriteId) {
console.log('Ouverture du slider depuis favoris:', favoriteId);
// Récupérer tous les favoris images
this.images = getFavoriteItems().filter(item => item.type === 'image');
if (this.images.length === 0) {
console.error('Aucun favori image trouvé');
return;
}
// Trouver l'index de l'image courante
this.currentIndex = this.images.findIndex(img => img.id === favoriteId);
if (this.currentIndex === -1) this.currentIndex = 0;
this.openSlider();
}
// Ouvrir avec une image spécifique depuis la galerie normale
openFromGallery(imageId, dayContainer) {
console.log('Ouverture du slider depuis galerie:', imageId, dayContainer);
// Récupérer toutes les images du jour courant
const dayImages = [];
// Chercher dans le container du jour (peut être .dynamic-card ou autre)
let photoItems;
if (dayContainer) {
photoItems = dayContainer.querySelectorAll('.photo-item');
} else {
// Fallback: chercher dans tout le document
photoItems = document.querySelectorAll('.photo-item');
}
console.log('Photo items trouvés:', photoItems.length);
photoItems.forEach(item => {
const img = item.querySelector('img');
const heartIcon = item.querySelector('.heart-icon');
console.log('Processing item:', {
img: !!img,
heartIcon: !!heartIcon,
imgSrc: img ? img.src : null,
heartIconId: heartIcon ? heartIcon.getAttribute('data-id') : null
});
if (img && heartIcon) {
dayImages.push({
id: heartIcon.getAttribute('data-id'),
url: heartIcon.getAttribute('data-path') || img.src,
description: heartIcon.getAttribute('data-description') || img.alt || 'Photo',
date: '', // Pas de date spécifique pour les images de galerie
type: 'image'
});
}
});
console.log('Images trouvées:', dayImages);
this.images = dayImages;
if (this.images.length === 0) {
console.error('Aucune image trouvée dans ce jour');
// Essayer de créer au moins l'image courante
const currentHeartIcon = document.querySelector(`[data-id="${imageId}"]`);
if (currentHeartIcon) {
const currentImg = currentHeartIcon.closest('.photo-item').querySelector('img');
if (currentImg) {
this.images = [{
id: imageId,
url: currentHeartIcon.getAttribute('data-path') || currentImg.src,
description: currentHeartIcon.getAttribute('data-description') || currentImg.alt || 'Photo',
date: '',
type: 'image'
}];
console.log('Image de fallback créée:', this.images);
}
}
if (this.images.length === 0) {
return;
}
}
// Trouver l'index de l'image courante
this.currentIndex = this.images.findIndex(img => img.id === imageId);
if (this.currentIndex === -1) this.currentIndex = 0;
console.log('Index trouvé:', this.currentIndex);
this.openSlider();
}
openSlider() {
console.log('=== openSlider appelée ===');
console.log('Index courant:', this.currentIndex, 'Total:', this.images.length);
console.log('Images à afficher:', this.images);
// Supprimer tout slider existant
const existingSlider = document.querySelector('.universal-slider');
if (existingSlider) {
console.log('Suppression du slider existant');
existingSlider.remove();
}
this.createSlider();
this.showSlide(this.currentIndex);
this.attachEvents();
this.isOpen = true;
// Animation d'ouverture immédiate et garantie avec diagnostic
setTimeout(() => {
if (window.diagnoseSliderDisplay) {
window.diagnoseSliderDisplay('#universalSlider');
}
}, 50);
}
createSlider() {
// ⚡ OPTIMISATION: Créer seulement si nécessaire
if (document.querySelector('#universalSlider')) {
return; // Déjà créé
}
console.log('Création slider pour', this.images.length, 'images');
// HTML optimisé et minimal
const sliderHTML = `
<div class="universal-slider" id="universalSlider" style="
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0,0,0,0.95) !important;
z-index: 9999 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
opacity: 1 !important;
visibility: visible !important;
">
<div class="slider-overlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer;"></div>
<!-- Header avec contrôles -->
<div class="slider-header" style="
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
gap: 15px;
z-index: 10001;
">
<button class="slider-btn close-btn" title="Fermer" style="
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
">
<i class="bi bi-x-lg"></i>
</button>
</div>
<!-- Navigation -->
<button class="slider-nav prev-btn" title="Précédent" style="
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
display: ${this.images.length > 1 ? 'flex' : 'none'};
align-items: center;
justify-content: center;
z-index: 10001;
transition: background 0.2s ease;
">
<i class="bi bi-arrow-left-circle-fill" style="font-size: 20px;"></i>
</button>
<button class="slider-nav next-btn" title="Suivant" style="
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
display: ${this.images.length > 1 ? 'flex' : 'none'};
align-items: center;
justify-content: center;
z-index: 10001;
transition: background 0.2s ease;
">
<i class="bi bi-arrow-right-circle-fill" style="font-size: 20px;"></i>
</button>
<!-- Container des slides -->
<div class="slider-container" style="
position: relative;
width: 90%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
">
<div class="slider-track" id="universalSliderTrack" style="
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
">
${this.images.map((img, index) => `
<div class="slide" data-index="${index}" style="
position: absolute;
width: 100%;
height: 100%;
display: ${index === 0 ? 'flex' : 'none'};
align-items: center;
justify-content: center;
flex-direction: column;
">
<div class="slide-content" style="
position: relative;
max-width: 100%;
max-height: 90%;
display: flex;
align-items: center;
justify-content: center;
">
<img src="${img.url}" alt="${img.description || 'Photo'}"
loading="${index <= 2 ? 'eager' : 'lazy'}"
draggable="false"
style="
max-width: 100%;
max-height: 100%;
object-fit: contain;
cursor: zoom-in;
transition: transform 0.3s ease;
user-select: none;
">
</div>
${img.description ? `
<div class="slide-info" style="
position: absolute;
bottom: 60px;
left: 50%;
transform: translateX(-50%);
text-align: center;
color: white;
background: rgba(0,0,0,0.6);
padding: 8px 16px;
border-radius: 20px;
max-width: 80%;
font-size: 14px;
">
${img.description}
</div>
` : ''}
</div>
`).join('')}
</div>
</div>
<!-- Galerie de thumbnails révolutionnaire -->
<div class="cinema-gallery" style="
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 180px;
background: linear-gradient(0deg, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.7) 50%, transparent 100%);
z-index: 10001;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 0 20px 20px;
">
<!-- Info header avec compteur élégant -->
<div class="gallery-header" style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
">
<div class="photo-counter" style="
color: white;
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
">
<div style="
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
animation: pulse 2s infinite;
"></div>
<span>${(this.currentIndex || 0) + 1}</span>
<span style="opacity: 0.6;">sur</span>
<span>${this.images.length}</span>
</div>
<div class="gallery-controls" style="
display: flex;
gap: 12px;
align-items: center;
">
<div style="color: rgba(255,255,255,0.7); font-size: 12px;">
Clic = Zoom 5x • Drag = Déplacer
</div>
</div>
</div>
<!-- Carrousel de thumbnails cinématographique -->
<div class="cinema-carousel" style="
position: relative;
height: 80px;
overflow: hidden;
">
<!-- Container scrollable -->
<div class="carousel-track" id="carouselTrack" style="
display: flex;
gap: 12px;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scroll-behavior: smooth;
scrollbar-width: none;
-ms-overflow-style: none;
padding: 0 50px;
">
${this.images.map((img, index) => `
<div class="cinema-thumb ${index === 0 ? 'active' : ''}"
data-index="${index}"
style="
flex: 0 0 auto;
width: ${index === 0 ? '100px' : '70px'};
height: 70px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
position: relative;
transition: transform 0.2s ease, opacity 0.2s ease;
transform: ${index === 0 ? 'translateY(-4px)' : 'translateY(0)'};
opacity: ${index === 0 ? '1' : '0.7'};
border: ${index === 0 ? '2px solid #10b981' : '1px solid rgba(255,255,255,0.3)'};
"
onmouseenter="
if (${index} !== (window.universalSlider?.currentIndex || 0)) {
this.style.opacity = '0.9';
}
"
onmouseleave="
if (${index} !== (window.universalSlider?.currentIndex || 0)) {
this.style.opacity = '0.7';
}
">
<!-- Image principale -->
<img src="${img.url}"
alt="${img.description || 'Photo'}"
loading="lazy"
decoding="async"
style="
width: 100%;
height: 100%;
object-fit: cover;
">
<!-- Numéro simplifié -->
<div class="thumb-number" style="
position: absolute;
top: 4px;
right: 4px;
background: ${index === 0 ? '#10b981' : 'rgba(0,0,0,0.8)'};
color: white;
font-size: 10px;
font-weight: 500;
padding: 2px 5px;
border-radius: 4px;
min-width: 16px;
text-align: center;
">
${index + 1}
</div>
<!-- Indicateur actif simplifié -->
${index === 0 ? `
<div class="active-indicator" style="
position: absolute;
bottom: -1px;
left: 50%;
transform: translateX(-50%);
width: 16px;
height: 2px;
background: #10b981;
border-radius: 1px;
"></div>
` : ''}
<!-- Preview au hover pour les non-actifs -->
${index !== 0 ? `
<div class="hover-preview" style="
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.9);
color: white;
font-size: 11px;
padding: 4px 8px;
border-radius: 6px;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
white-space: nowrap;
z-index: 1000;
">
Photo ${index + 1}
</div>
` : ''}
</div>
`).join('')}
</div>
<!-- Gradients de fade sur les côtés -->
<div style="
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 50px;
background: linear-gradient(90deg, rgba(0,0,0,0.8), transparent);
pointer-events: none;
z-index: 1;
"></div>
<div style="
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 50px;
background: linear-gradient(-90deg, rgba(0,0,0,0.8), transparent);
pointer-events: none;
z-index: 1;
"></div>
</div>
</div>
<!-- Animations CSS Optimisées -->
<style>
/* Optimisation: animations réduites et ciblées */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Suppression du glow coûteux - remplacé par border simple */
.cinema-thumb.active .active-indicator {
background: #10b981;
animation: none; /* Suppression de l'animation glow */
}
/* Hover optimisé - moins d'effets */
.cinema-thumb:hover .hover-preview {
opacity: 1 !important;
bottom: -35px !important;
}
/* Masquer scrollbar */
.carousel-track::-webkit-scrollbar {
display: none;
}
/* Optimisation: will-change pour les éléments animés */
.cinema-thumb {
will-change: transform, filter;
}
.progress-bar {
will-change: width;
}
</style>
</div>
`;
// Injecter dans le DOM
console.log('Ajout du slider au DOM');
document.body.insertAdjacentHTML('beforeend', sliderHTML);
// Vérifier que le slider a été ajouté
const addedSlider = document.querySelector('#universalSlider');
console.log('Slider ajouté:', !!addedSlider);
if (addedSlider) {
console.log('Slider trouvé dans le DOM');
console.log('Styles du slider:', {
display: addedSlider.style.display,
opacity: addedSlider.style.opacity,
visibility: addedSlider.style.visibility,
zIndex: addedSlider.style.zIndex,
position: addedSlider.style.position
});
console.log('Slider dans viewport:', addedSlider.getBoundingClientRect());
} else {
console.error('Erreur: slider non trouvé après ajout');
}
}
showSlide(index) {
console.log('=== showSlide appelée ===', 'index:', index, 'total:', this.images.length);
if (index < 0 || index >= this.images.length) {
console.log('Index invalide, arrêt');
return;
}
this.currentIndex = index;
// Masquer toutes les slides et afficher seulement la courante
const slides = document.querySelectorAll('#universalSlider .slide');
console.log('Slides trouvées:', slides.length);
slides.forEach((slide, i) => {
slide.style.display = i === index ? 'flex' : 'none';
console.log(`Slide ${i}:`, i === index ? 'visible' : 'cachée');
});
// Mettre à jour le compteur
const currentSlideSpan = document.querySelector('#universalSlider .current-slide');
if (currentSlideSpan) {
currentSlideSpan.textContent = index + 1;
console.log('Compteur mis à jour:', index + 1);
}
// Mettre à jour les boutons de navigation
const prevBtn = document.querySelector('#universalSlider .prev-btn');
const nextBtn = document.querySelector('#universalSlider .next-btn');
if (prevBtn) {
prevBtn.style.opacity = index > 0 ? '1' : '0.5';
}
if (nextBtn) {
nextBtn.style.opacity = index < this.images.length - 1 ? '1' : '0.5';
}
// Mettre à jour la galerie cinématographique
this.updateCinemaGallery(index);
// Mettre à jour le bouton favori
this.updateFavoriteButton();
}
updateCinemaGallery(activeIndex) {
// Mettre à jour la progress bar (optimisé)
const progressBar = document.querySelector('.progress-bar');
if (progressBar) {
progressBar.style.width = `${(activeIndex + 1) / this.images.length * 100}%`;
}
// Mettre à jour le compteur (optimisé)
const counter = document.querySelector('.photo-counter span');
if (counter) {
counter.textContent = activeIndex + 1;
}
// Mettre à jour les thumbnails - optimisé avec moins de calculs
const cinemaThumbs = document.querySelectorAll('.cinema-thumb');
cinemaThumbs.forEach((thumb, i) => {
const isActive = i === activeIndex;
// Optimisation: changements minimaux
if (isActive) {
thumb.style.width = '100px';
thumb.style.transform = 'translateY(-4px)';
thumb.style.opacity = '1';
thumb.style.borderColor = '#10b981';
thumb.style.borderWidth = '2px';
// Numéro actif - simplifié
const number = thumb.querySelector('.thumb-number');
if (number) {
number.style.background = '#10b981';
}
} else {
thumb.style.width = '70px';
thumb.style.transform = 'translateY(0)';
thumb.style.opacity = '0.7';
thumb.style.borderColor = 'rgba(255,255,255,0.3)';
thumb.style.borderWidth = '1px';
// Numéro inactif - simplifié
const number = thumb.querySelector('.thumb-number');
if (number) {
number.style.background = 'rgba(0,0,0,0.8)';
}
}
});
// Auto-scroll optimisé avec throttling
if (!this.scrollTimeout) {
this.scrollTimeout = setTimeout(() => {
const track = document.getElementById('carouselTrack');
const activeThumb = document.querySelector(`.cinema-thumb[data-index="${activeIndex}"]`);
if (track && activeThumb) {
const scrollLeft = activeThumb.offsetLeft - (track.clientWidth / 2) + (activeThumb.clientWidth / 2);
track.scrollTo({
left: scrollLeft,
behavior: 'smooth'
});
}
this.scrollTimeout = null;
}, 50); // Throttle à 50ms
}
}
updateFavoriteButton() {
const currentImage = this.images[this.currentIndex];
const favoriteBtn = document.querySelector('.slider-controls .favorite-btn');
if (favoriteBtn && currentImage) {
favoriteBtn.setAttribute('data-id', currentImage.id);
// Vérifier si l'image est déjà en favoris
const heartIcon = document.querySelector(`[data-id="${currentImage.id}"]`);
const isFavorite = heartIcon && heartIcon.querySelector('i.bi-heart-fill');
const icon = favoriteBtn.querySelector('i');
if (isFavorite) {
icon.className = 'bi bi-heart-fill';
favoriteBtn.style.color = '#e91e63';
} else {
icon.className = 'bi bi-heart';
favoriteBtn.style.color = 'white';
}
}
}
nextSlide() {
const nextIndex = (this.currentIndex + 1) % this.images.length;
this.showSlide(nextIndex);
}
prevSlide() {
const prevIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
this.showSlide(prevIndex);
}
close() {
if (!this.isOpen) return;
const slider = document.querySelector('.universal-slider');
if (slider) {
slider.classList.remove('active');
setTimeout(() => {
slider.remove();
this.isOpen = false;
}, 300);
}
}
attachEvents() {
console.log('=== attachEvents appelée ===');
const slider = document.getElementById('universalSlider');
if (!slider) {
console.error('Slider non trouvé pour attachEvents');
return;
}
console.log('Slider trouvé pour attachEvents');
// Bouton fermer
const closeBtn = slider.querySelector('.close-btn');
if (closeBtn) {
console.log('Bouton fermer trouvé');
closeBtn.addEventListener('click', () => {
console.log('Clic fermer');
this.close();
});
}
// Navigation
const prevBtn = slider.querySelector('.prev-btn');
const nextBtn = slider.querySelector('.next-btn');
if (prevBtn) {
console.log('Bouton précédent trouvé');
prevBtn.addEventListener('click', () => {
console.log('Clic précédent');
this.prevSlide();
});
}
if (nextBtn) {
console.log('Bouton suivant trouvé');
nextBtn.addEventListener('click', () => {
console.log('Clic suivant');
this.nextSlide();
});
}
// Navigation cinématographique des thumbnails
const cinemaThumbs = slider.querySelectorAll('.cinema-thumb');
cinemaThumbs.forEach((thumb, index) => {
thumb.addEventListener('click', () => {
console.log('Clic cinema thumbnail:', index);
this.showSlide(index);
this.updateCinemaGallery(index);
});
});
console.log('Cinema thumbnails cliquables:', cinemaThumbs.length);
// Clic sur overlay pour fermer
const overlay = slider.querySelector('.slider-overlay');
if (overlay) {
overlay.addEventListener('click', () => {
console.log('Clic overlay');
this.close();
});
}
// Clavier
this.keyboardHandler = this.handleKeyboard.bind(this);
document.addEventListener('keydown', this.keyboardHandler);
console.log('Événements clavier attachés');
// Zoom sur les images
this.attachZoomEvents(slider);
console.log('Événements zoom attachés');
console.log('Événements attachés avec succès');
}
close() {
console.log('=== close appelée ===');
const slider = document.querySelector('.universal-slider');
if (slider) {
console.log('Fermeture du slider');
// Animation de fermeture
slider.style.opacity = '0';
setTimeout(() => {
slider.remove();
console.log('Slider supprimé du DOM');
// Nettoyer les événements
if (this.keyboardHandler) {
document.removeEventListener('keydown', this.keyboardHandler);
this.keyboardHandler = null;
}
this.isOpen = false;
}, 300);
}
}
nextSlide() {
if (this.currentIndex < this.images.length - 1) {
this.showSlide(this.currentIndex + 1);
}
}
prevSlide() {
if (this.currentIndex > 0) {
this.showSlide(this.currentIndex - 1);
}
}
handleKeyboard(e) {
if (!this.isOpen) return;
switch (e.key) {
case 'Escape':
this.close();
break;
case 'ArrowLeft':
this.prevSlide();
break;
case 'ArrowRight':
this.nextSlide();
break;
}
}
attachZoomEvents(slider) {
// Fonctionnalité de zoom à 5 niveaux pour les images
const images = slider.querySelectorAll('.slide img');
images.forEach(img => {
let zoomLevel = 1; // 1x, 1.5x, 2x, 3x, 4x, 5x
let isDragging = false;
let startX = 0;
let startY = 0;
let translateX = 0;
let translateY = 0;
// Clic simple pour zoom progressif (5 niveaux)
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Cycle à travers 5 niveaux de zoom
switch(zoomLevel) {
case 1: zoomLevel = 1.5; break; // 1x → 1.5x
case 1.5: zoomLevel = 2; break; // 1.5x → 2x
case 2: zoomLevel = 3; break; // 2x → 3x
case 3: zoomLevel = 4; break; // 3x → 4x
case 4: zoomLevel = 5; break; // 4x → 5x
case 5:
zoomLevel = 1; // 5x → 1x (reset)
translateX = 0;
translateY = 0;
break;
}
console.log('Zoom niveau:', zoomLevel);
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
// Curseur selon le niveau
if (zoomLevel === 1) {
img.style.cursor = 'zoom-in';
img.removeAttribute('data-zoomed');
} else if (zoomLevel === 5) {
img.style.cursor = 'zoom-out';
img.setAttribute('data-zoomed', zoomLevel);
} else {
img.style.cursor = 'zoom-in';
img.setAttribute('data-zoomed', zoomLevel);
}
});
// Drag pour déplacer l'image zoomée
img.addEventListener('mousedown', (e) => {
if (zoomLevel > 1) {
isDragging = true;
startX = e.clientX - translateX;
startY = e.clientY - translateY;
img.style.cursor = 'grabbing';
e.preventDefault();
e.stopPropagation();
}
});
// Mousemove global pour le drag
const handleMouseMove = (e) => {
if (isDragging && zoomLevel > 1) {
translateX = e.clientX - startX;
translateY = e.clientY - startY;
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
}
};
// Mouseup global pour arrêter le drag
const handleMouseUp = () => {
if (isDragging) {
isDragging = false;
if (zoomLevel === 1) {
img.style.cursor = 'zoom-in';
} else if (zoomLevel === 5) {
img.style.cursor = 'zoom-out';
} else {
img.style.cursor = 'zoom-in';
}
}
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
// Touch support pour mobile
img.addEventListener('touchstart', (e) => {
if (zoomLevel > 1 && e.touches.length === 1) {
isDragging = true;
const touch = e.touches[0];
startX = touch.clientX - translateX;
startY = touch.clientY - translateY;
e.preventDefault();
}
});
img.addEventListener('touchmove', (e) => {
if (isDragging && zoomLevel > 1 && e.touches.length === 1) {
const touch = e.touches[0];
translateX = touch.clientX - startX;
translateY = touch.clientY - startY;
img.style.transform = `scale(${zoomLevel}) translate(${translateX}px, ${translateY}px)`;
e.preventDefault();
}
});
img.addEventListener('touchend', () => {
isDragging = false;
});
// Initialiser le curseur
img.style.cursor = 'zoom-in';
});
}
}
// ⚡ OPTIMISATION MAJEURE: Sliders lazy (créés seulement quand utilisés)
let favoritesSlider = null;
let universalSlider = null;
// Fonctions pour créer les sliders à la demande
function getFavoritesSlider() {
if (!favoritesSlider) {
favoritesSlider = new FavoritesSlider();
}
return favoritesSlider;
}
function getUniversalSlider() {
if (!universalSlider) {
universalSlider = new UniversalImageSlider();
}
return universalSlider;
}
// Fonction pour voir un favori avec le slider plein écran
function viewFavorite(id) {
console.log('Ouverture du slider pour favori:', id);
// ⚡ OPTIMISÉ: Utiliser le slider lazy
const slider = getUniversalSlider();
if (slider && slider.openFromFavorites) {
slider.openFromFavorites(id);
} else {
console.error('Erreur slider favoris');
}
}
// Fonction pour voir une image depuis la galerie normale
function viewImage(imageId, clickedElement) {
console.log('=== viewImage appelée ===');
console.log('imageId:', imageId);
console.log('clickedElement:', clickedElement);
// Trouver le container du jour - peut être plusieurs niveaux au-dessus
let dayContainer = clickedElement;
// Remonter jusqu'à trouver un container approprié
while (dayContainer && dayContainer !== document.body) {
console.log('Checking element:', dayContainer.tagName, dayContainer.className, dayContainer.id);
if (dayContainer && dayContainer.classList && (
dayContainer.classList.contains('dynamic-card') ||
dayContainer.classList.contains('collapse') ||
dayContainer.id && dayContainer.id.startsWith('demP')
)) {
break;
}
dayContainer = dayContainer.parentElement;
}
// ⚡ OPTIMISÉ: Logs réduits pour performance
// ⚡ OPTIMISÉ: Utiliser le slider lazy
const slider = getUniversalSlider();
if (slider && slider.openFromGallery) {
slider.openFromGallery(imageId, dayContainer);
} else {
console.error('Erreur slider galerie');
}
}
// ⚡ OPTIMISÉ: Fonctions globales avec sliders lazy
window.viewImage = viewImage;
window.viewFavorite = viewFavorite;
window.getUniversalSlider = getUniversalSlider; // Fonction lazy au lieu de l'instance
// Variables globales pour éviter les ReferenceError
if (typeof favoriteCount === 'undefined') {
window.favoriteCount = 0;
}
if (typeof favButton === 'undefined') {
window.favButton = null;
}
if (typeof favoriteButton === 'undefined') {
window.favoriteButton = null;
}
if (typeof purchaseAlertTimeout === 'undefined') {
window.purchaseAlertTimeout = null;
}
// Définir favoriteCount globalement pour éviter les ReferenceError
let favoriteCount = window.favoriteCount || 0;
// Fonction de sécurité pour vérifier les éléments DOM
function safeAddEventListener(selector, event, callback) {
const element = document.querySelector(selector);
if (element) {
element.addEventListener(event, callback);
} else {
console.warn(`Élément non trouvé: ${selector}`);
}
}
// Fonction utilitaire pour manipuler classList de manière sécurisée
function safeClassList(element, action, ...classes) {
if (!element || !element.classList) {
console.warn('Élément ou classList invalide:', element);
return false;
}
try {
switch(action) {
case 'add':
element.classList.add(...classes);
break;
case 'remove':
element.classList.remove(...classes);
break;
case 'toggle':
return element.classList.toggle(classes[0], classes[1]);
case 'contains':
return element.classList.contains(classes[0]);
default:
console.warn('Action classList inconnue:', action);
return false;
}
return true;
} catch (error) {
console.warn('Erreur lors de la manipulation de classList:', error);
return false;
}
}
// Rendre la fonction utilitaire globale
window.safeClassList = safeClassList;
// Fonction pour sécuriser automatiquement tous les accès à classList
function secureClassListAccess() {
// Intercepter les erreurs classList globalement
window.addEventListener('error', function(e) {
if (e.message && (e.message.includes('classList') || e.message.includes('Cannot read properties of null'))) {
console.warn('Erreur DOM interceptée et corrigée:', e.message, 'Ligne:', e.lineno, 'Fichier:', e.filename);
e.preventDefault(); // Empêcher l'erreur de se propager
return true;
}
});
// Intercepter les erreurs non catchées
window.addEventListener('unhandledrejection', function(e) {
if (e.reason && e.reason.message && e.reason.message.includes('classList')) {
console.warn('Promise rejection classList interceptée:', e.reason.message);
e.preventDefault();
return true;
}
});
console.log('Système de sécurisation DOM/classList activé');
}
// Activer la sécurisation
secureClassListAccess();
// Fonction de diagnostic pour vérifier l'affichage du slider
function diagnoseSliderDisplay(sliderId) {
const slider = document.querySelector(sliderId);
if (!slider) {
console.error('Diagnostic: Slider non trouvé -', sliderId);
return false;
}
const styles = window.getComputedStyle(slider);
const rect = slider.getBoundingClientRect();
console.log('=== DIAGNOSTIC SLIDER ===', sliderId);
console.log('Élément présent:', !!slider);
console.log('Styles calculés:', {
display: styles.display,
opacity: styles.opacity,
visibility: styles.visibility,
zIndex: styles.zIndex,
position: styles.position
});
console.log('Position/Taille:', rect);
console.log('Dans le viewport:', rect.width > 0 && rect.height > 0);
console.log('Parent:', slider.parentElement?.tagName);
// Vérifier les règles CSS qui pourraient masquer le slider
const hiddenReasons = [];
if (styles.display === 'none') hiddenReasons.push('display: none');
if (styles.opacity === '0') hiddenReasons.push('opacity: 0');
if (styles.visibility === 'hidden') hiddenReasons.push('visibility: hidden');
if (parseInt(styles.zIndex) < 0) hiddenReasons.push('z-index négatif');
if (hiddenReasons.length > 0) {
console.warn('Raisons du masquage:', hiddenReasons);
// Forcer l'affichage
console.log('Forçage de l\'affichage...');
slider.style.display = 'flex';
slider.style.opacity = '1';
slider.style.visibility = 'visible';
slider.style.zIndex = '9999';
slider.style.position = 'fixed';
slider.style.top = '0';
slider.style.left = '0';
slider.style.width = '100%';
slider.style.height = '100%';
console.log('Slider forcé visible');
} else {
console.log('✅ Slider devrait être visible');
}
return true;
}
// Rendre la fonction globale
window.diagnoseSliderDisplay = diagnoseSliderDisplay;
// Fonction de sécurité pour checkFavorites
function checkFavorites() {
try {
let count = 0;
if (typeof getFavoriteItems === 'function') {
count = getFavoriteItems().length;
} else {
// Fallback : compter depuis le DOM
const giftCount = document.getElementById('giftCount');
if (giftCount && giftCount.textContent) {
count = parseInt(giftCount.textContent.trim()) || 0;
}
}
// Mettre à jour les variables globales
window.favoriteCount = count;
favoriteCount = count;
console.log('Favoris vérifiés:', count);
return count;
} catch (error) {
console.warn('Erreur lors de la vérification des favoris:', error);
return 0;
}
}
// Rendre checkFavorites globale si elle n'existe pas
if (typeof window.checkFavorites === 'undefined') {
window.checkFavorites = checkFavorites;
}
// Initialisation sécurisée après chargement du DOM
document.addEventListener('DOMContentLoaded', function() {
console.log('=== DOM chargé, initialisation des sliders ===');
console.log('viewImage disponible:', typeof window.viewImage);
console.log('viewFavorite disponible:', typeof window.viewFavorite);
console.log('universalSlider disponible:', typeof window.universalSlider);
// Vérifier que les éléments critiques existent
const criticalElements = [
'filtre_photos_voir',
// Ajoutez d'autres IDs critiques ici si nécessaire
];
criticalElements.forEach(id => {
const element = document.getElementById(id);
if (element) {
console.log(`✓ Élément trouvé: ${id}`);
} else {
console.warn(`⚠ Élément manquant: ${id}`);
}
});
// Initialiser checkFavorites si la fonction existe
if (typeof window.checkFavorites === 'function') {
window.checkFavorites();
}
});
// Fonction pour obtenir l'image d'un produit selon son type
function getProductImage(productId) {
const productImages = {
'album': '/images/produit/Album5sur5-3.jpg',
'digital': '/images/produit/photoNumerique.jpg',
'prints': '/images/produit/PochettePhoto5sur5-2.jpg',
'calendrier': '/images/produit/Calendrier5sur5-1.jpg',
'livre': '/images/produit/LivreSouvenir5sur5-1.jpg',
'coffret': '/images/produit/CoffretCadeau5sur5-2.jpg',
'retro': '/images/produit/PochetteRetro5sur5-1.jpg'
};
return productImages[productId] || '/images/produit/albumm.PNG';
}
// Fonction pour générer les suggestions de produits (synchronisées avec ecommerce-sidebar)
function generateProductSuggestions(favoriteCount) {
if (favoriteCount === 0) {
return `
<div class="no-favorites-cta">
<div class="cta-content">
<i class="bi bi-heart" style="font-size: 4rem; color: #e91e63; margin-bottom: 1rem;"></i>
<h3>Commencez par sélectionner vos photos préférées</h3>
<p>Cliquez sur <i class="bi bi-heart" style="color: #e91e63;"></i> sur vos plus beaux souvenirs pour débloquer nos produits personnalisés.</p>
<button class="btn-start-selection" onclick="window.showDaysView()">
<i class="bi bi-images"></i>
Parcourir les photos
</button>
</div>
</div>
`;
}
let suggestionsHTML = '<div class="suggestions-header"><h4><i class="bi bi-gift"></i> Produits recommandés pour vous</h4></div>';
// Utiliser les mêmes produits que dans ecommerce-sidebar
const products = [];
// Album Photo Premium (toujours disponible)
products.push({
id: 'album',
name: 'Album Photo Premium',
description: `Album photo personnalisé avec vos ${favoriteCount} photos favorites. Papier de qualité supérieure, reliure rigide.`,
currentPrice: '24,90€',
originalPrice: '28,90€',
savings: '-14%',
badge: 'Populaire',
available: favoriteCount > 0,
disabled: favoriteCount === 0
});
// Pack Numérique HD (toujours disponible)
products.push({
id: 'digital',
name: 'Pack Numérique HD',
description: `Téléchargement immédiat de vos photos en haute définition. Qualité professionnelle garantie.`,
currentPrice: '3,90€',
originalPrice: '',
savings: '',
badge: 'Nouveau',
available: favoriteCount > 0,
disabled: favoriteCount === 0
});
// Tirages Premium (disponible si >= 12 favoris)
products.push({
id: 'prints',
name: 'Tirages Premium',
description: `${Math.min(favoriteCount, 12)} tirages photo 10x15cm de vos favoris. Papier brillant professionnel, livraison gratuite.`,
currentPrice: '9.9€',
originalPrice: '',
savings: '',
badge: 'Débloqué !',
available: true,
disabled: false
});
suggestionsHTML += '<div class="products-grid">';
products.forEach(product => {
suggestionsHTML += `
<div class="product-card ${product.disabled ? 'disabled' : ''}" data-product="${product.id}">
<div class="product-badge ${product.disabled ? 'disabled' : ''}">${product.badge}</div>
<div class="product-image">
<img src="${getProductImage(product.id)}" alt="${product.name}" onerror="this.src='/images/produit/albumm.PNG'">
</div>
<div class="product-info">
<h5>${product.name}</h5>
<p>${product.description}</p>
<div class="product-pricing">
<span class="current-price">${product.currentPrice}</span>
${product.originalPrice ? `<span class="original-price">${product.originalPrice}</span>` : ''}
${product.savings ? `<span class="savings">${product.savings}</span>` : ''}
</div>
<button class="btn-add-to-cart ${product.disabled ? 'disabled' : ''}"
onclick="orderProduct('${product.id}')"
${product.disabled ? 'disabled title="Ajoutez des favoris pour commander"' : ''}>
<i class="bi bi-cart-plus"></i>
${product.disabled ? 'Ajoutez des favoris' : 'Commander'}
</button>
</div>
</div>
`;
});
suggestionsHTML += '</div>';
// Message d'encouragement
if (favoriteCount < 12) {
const remaining = 12 - favoriteCount;
suggestionsHTML += `
<div class="encouragement-card">
<div class="encouragement-content">
<div class="encouragement-icon">🎯</div>
<div class="encouragement-text">
<h5>Encore ${remaining} photo${remaining > 1 ? 's' : ''} pour débloquer les Tirages Premium !</h5>
<p>Sélectionnez ${remaining} photo${remaining > 1 ? 's' : ''} supplémentaire${remaining > 1 ? 's' : ''} pour accéder à tous nos produits</p>
</div>
</div>
</div>
`;
}
return suggestionsHTML;
}
// Fonction pour ajouter au panier
function addToCart(productId) {
// Animation du bouton cadeau
const giftButton = document.querySelector('.gift-button');
if (giftButton) {
if (giftButton.classList) {
giftButton.classList.add('active');
setTimeout(() => {
if (giftButton.classList) {
giftButton.classList.remove('active');
}
}, 600);
}
}
// Afficher une notification
showNotification(`Produit ajouté au panier !`, 'success');
// Ici vous pourriez ajouter la logique d'ajout au panier réel
console.log('Ajout au panier:', productId);
}
// Fonction pour afficher une notification
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
<i class="bi bi-check-circle"></i>
<span>${message}</span>
`;
document.body.appendChild(notification);
setTimeout(() => {
if (notification && notification.classList) {
notification.classList.add('show');
}
}, 100);
setTimeout(() => {
if (notification && notification.classList) {
notification.classList.remove('show');
setTimeout(() => {
if (document.body.contains(notification)) {
document.body.removeChild(notification);
}
}, 300);
}
}, 3000);
}
// Fonction pour obtenir le nombre actuel de favoris
function getCurrentFavoriteCount() {
return getFavoriteItems().length;
}
// Gestion du localStorage pour l'indicateur "nouveau contenu"
const lastVisitKey = 'lastVisitISO';
const currentVisit = new Date().toISOString();
// Sauvegarder la visite actuelle
localStorage.setItem(lastVisitKey, currentVisit);
// Vérifier et marquer les jours avec nouveau contenu
const dateCards = document.querySelectorAll('.date-card.modern-card');
dateCards.forEach(card => {
// Ici vous devriez comparer avec updatedAt du backend
// Pour l'instant, on simule avec un attribut data-updated
const updatedAt = card.getAttribute('data-updated');
if (updatedAt) {
const lastVisit = localStorage.getItem(lastVisitKey);
if (new Date(updatedAt) > new Date(lastVisit)) {
if (card && card.classList) {
card.classList.add('has-new-content');
}
// Ajouter le badge "nouveau" si pas déjà présent
if (!card.querySelector('.badge-new')) {
const badge = document.createElement('span');
badge.className = 'badge-new';
badge.setAttribute('aria-label', 'Nouveau contenu');
card.querySelector('.title-line').appendChild(badge);
}
}
}
});
// Gestion des aria-label dynamiques pour les cartes de jours
dateCards.forEach(card => {
const dayText = card.querySelector('.day').textContent.trim();
const dateText = card.querySelector('.full-date').textContent.trim();
const photos = card.querySelector('.media-list-horizontal li:nth-child(1)')?.textContent.trim() || '0';
const audios = card.querySelector('.media-list-horizontal li:nth-child(2)')?.textContent.trim() || '0';
const videos = card.querySelector('.media-list-horizontal li:nth-child(3)')?.textContent.trim() || '0';
const ariaLabel = `Contenu du ${dateText} : ${photos} photos, ${audios} audios, ${videos} vidéos`;
card.setAttribute('aria-label', ariaLabel);
});
});
</script>
<!-- Make sure we're showing the right content by default if no valid content is marked 'show' -->
{% if lastValidIndex > 0 %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if no content is showing
if (document.querySelectorAll('.container--gallery .collapse.show').length === 0) {
// Show the content for the last valid date
var lastValidContent = document.getElementById('demP{{ lastValidIndex }}');
if (lastValidContent) {
if (lastValidContent.classList) {
lastValidContent.classList.add('show');
}
// Also mark the corresponding date card as active
var dateCards = document.querySelectorAll('.date-card');
if (dateCards.length >= {{ lastValidIndex }}) {
dateCards.forEach(card => {
if (card && card.classList) {
card.classList.remove('active');
}
});
const targetCard = dateCards[{{ lastValidIndex - 1 }}];
if (targetCard && targetCard.classList) {
targetCard.classList.add('active');
}
}
}
}
});
</script>
{% endif %}
</div>
</div>
{% endblock %} {% block Javascript %}
{{ parent() }}
<script>// Gestion de la sidebar des favoris
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('favorites-sidebar');
const openBtn = document.getElementById('openFavoritesSidebar');
const closeBtn = document.querySelector('.favorites-close');
const giftButton = document.querySelector('.gift-button');
function openSidebar() {
if (sidebar && sidebar.classList) {
sidebar.classList.add('active');
updateFavoritesSidebar();
}
}
function closeSidebar() {
if (sidebar && sidebar.classList) {
sidebar.classList.remove('active');
}
}
function updateFavoritesSidebar() {
const grid = document.getElementById('favorites-grid');
const counter = document.getElementById('favorites-counter');
const emptyState = document.getElementById('favorites-empty-state');
const progress = document.getElementById('favorites-progress');
const count = parseInt(document.getElementById('likeCount').textContent);
counter.textContent = count +1;
if (count === 0) {
emptyState.style.display = 'flex';
grid.style.display = 'none';
} else {
emptyState.style.display = 'none';
grid.style.display = 'grid';
const percentage = (count / 20) * 100;
progress.style.width = `${percentage}%`;
}
}
if (openBtn) {
openBtn.addEventListener('click', openSidebar);
}
if (favButton) {
favButton.addEventListener('click', openSidebar);
}
if (closeBtn) {
closeBtn.addEventListener('click', closeSidebar);
}
document.addEventListener('click', (e) => {
if (sidebar && sidebar.classList && sidebar.classList.contains('active') &&
!sidebar.contains(e.target) &&
(!favButton || !favButton.contains(e.target)) &&
(!openBtn || !openBtn.contains(e.target))) {
closeSidebar();
}
});
});
// Modification des fonctions existantes
const originalAddFavoris = window.AddFavoris;
window.AddFavoris = function($id, $idSejour, $urlimg, $description) {
if (originalAddFavoris) {
originalAddFavoris($id, $idSejour, $urlimg, $description);
}
// Vérifier si la sidebar est ouverte - noter que nous vérifions la valeur CSS right: 0
if ($("#favorites-sidebar").css("right") === "0px") {
// Recharger les favoris
loadFavorites();
}
};
const originalSupprimerFavoris = window.supprimerFavoris;
window.supprimerFavoris = function($id, $idSejour) {
if (originalSupprimerFavoris) {
originalSupprimerFavoris($id, $idSejour);
}
// Mettre à jour tous les compteurs de favoris
updateAllFavoriteCounters();
// Vérifier si la sidebar est ouverte
if ($("#favorites-sidebar").css("right") === "0px") {
// Recharger les favoris
loadFavorites();
}
};
// Variables globales
let selectedFavorites = [];
let allFavorites = [];
// Fonction pour mettre à jour la sidebar
function loadFavorites() {
$("#favorites-grid").html("<div style='text-align:center;padding:20px;'>Chargement...</div>");
$.ajax({
url: "/Parent/mes-favoris",
type: "GET",
dataType: "json",
success: function(data) {
$("#favorites-grid").empty();
allFavorites = data.data || [];
if (allFavorites.length > 0) {
$("#favorites-empty-state").hide();
$.each(allFavorites, function(i, fav) {
var isSelected = selectedFavorites.includes(fav.id);
var item = $("<div class='favorite-item' style='position:relative;border-radius:8px;overflow:hidden;aspect-ratio:1;cursor:pointer;'></div>");
var img = $("<img style='width:100%;height:100%;object-fit:cover;transition:transform 0.3s ease;'>").attr("src", fav.path).attr("alt", fav.descreption || "Photo favorite");
// Overlay de sélection
var selectionOverlay = $("<div class='selection-overlay' style='position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.3);display:flex;align-items:center;justify-content:center;'></div>");
// Icône de sélection
var checkIcon = $("<div style='width:25px;height:25px;border-radius:50%;border:2px solid white;display:flex;align-items:center;justify-content:center;background:" + (isSelected ? "#F56040" : "transparent") + ";transition:background 0.2s;'></div>");
if (isSelected) {
checkIcon.append("<i class='bi bi-check' style='color:white;font-size:16px;'></i>");
}
selectionOverlay.append(checkIcon);
// Overlay d'action (bouton supprimer)
var actionOverlay = $("<div class='action-overlay' style='position:absolute;top:5px;right:5px;opacity:0;transition:opacity 0.2s;'></div>");
actionOverlay.append(deleteBtn);
// Ajouter les événements
item.hover(
function() { $(this).find(".action-overlay").css("opacity", "1"); },
function() { $(this).find(".action-overlay").css("opacity", "0"); }
);
item.click(function() {
toggleSelection(fav.id, $(this).find(".selection-overlay > div"));
});
item.append(img).append(selectionOverlay).append(actionOverlay);
$("#favorites-grid").append(item);
});
$("#favorites-counter").text(allFavorites.length);
// Mettre à jour le compteur sur le bouton également
$("#openFavoritesSidebar span").text(allFavorites.length);
// Mettre à jour l'interface produits
updateProductsView();
} else {
$("#favorites-empty-state").show();
$("#favorites-counter").text("0");
$("#openFavoritesSidebar span").text("0");
$("#selection-count").text("0");
updateProductsView();
}
},
error: function() {
$("#favorites-grid").html("<div style='color:red;text-align:center;padding:20px;'>Erreur de chargement</div>");
}
});
}
// Fonction pour supprimer un favori
function removeFavorite(id) {
$.ajax({
url: "/Parent/remove-favorite/" + id,
type: "POST",
success: function() {
// Retirer de la sélection si présent
selectedFavorites = selectedFavorites.filter(favId => favId != id);
// Mettre à jour le compteur de sélection
$("#selection-count").text(selectedFavorites.length);
// Recharger les favoris
loadFavorites();
// Mettre à jour tous les compteurs de favoris
updateAllFavoriteCounters();
// Mettre à jour l'interface produits
updateProductsView();
},
error: function() {
alert("Erreur lors de la suppression du favori");
}
});
}
// Fonction pour basculer la sélection d'une photo
function toggleSelection(id, checkElement) {
const index = selectedFavorites.indexOf(id);
if (index === -1) {
// Ajouter à la sélection
selectedFavorites.push(id);
checkElement.css("background", "#F56040");
checkElement.html("<i class='bi bi-check' style='color:white;font-size:16px;'></i>");
} else {
// Retirer de la sélection
selectedFavorites.splice(index, 1);
checkElement.css("background", "transparent");
checkElement.html("");
}
// Mettre à jour le compteur
$("#selection-count").text(selectedFavorites.length);
// Mettre à jour l'interface produits
updateProductsView();
}
// Fonction pour mettre à jour la vue des produits
function updateProductsView() {
const current = selectedFavorites.length;
$("#product-photo-count").text(current);
let remainingForAlbum = Math.max(0, 20 - current);
let remainingForPochette = Math.max(0, 12 - current);
let remainingForPack = Math.max(0, 12 - current);
const progressBar = (count, total, color) => `
<div style="margin: 5px 0;">
<div style="background-color: #e9ecef; border-radius: 5px; overflow: hidden; height: 8px;">
<div style="width: ${(count / total) * 100}%; background-color: ${color}; height: 100%;"></div>
</div>
<small style="font-size: 12px;">${count}/${total} photos</small>
</div>
`;
// Liste des produits
const products = [
{
name: "Pack numérique (20 photos)",
required: 20,
remaining: Math.max(0, 20 - current),
image: "/images/produit/photoNumerique.jpg",
color: "#4caf50",
link: "{{ path('PackPhotosNumerique_Favoris', {'nbr':20}) }}",
},
+
{
name: "Pochette photo (12 photos)",
required: 12,
remaining: Math.max(0, 12 - current),
image: "/images/produit/PochettePhoto5sur5-2.jpg",
color: "#2196f3",
link: "{{ path('AjoutPochettePhotos_Favoris', {'nbr': 12}) }}",
},
].sort((a, b) => a.remaining - b.remaining);
const productList = products
.map((product) => {
const count = current;
const total = product.required;
const remaining = product.remaining;
return `
<li style="margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 10px;">
<img src="${product.image}" alt="${product.name}" style="width: 70px; height: 70px; border-radius: 5px; object-fit: cover;" />
<div style="flex: 1;">
<strong style="font-size: 14px;">${product.name}</strong>
${progressBar(count, total, product.color)}
${
`<small style="color: ${product.color}; font-size: 12px;">
Encore ${remaining} photos pour compléter ${product.name.toLowerCase()}
</small>
<button
style="
margin-top: 5px;
padding: 6px 12px;
background-color: ${product.color};
color: white;
border: none;
border-radius: 5px;
font-size: 13px;
cursor: pointer;
"
onclick="window.location.href='${product.link}'"
>
Commander
</button>`
}
</div>
</div>
</li>
`;
})
.join("");
const boutiqueButton = `
<li style="margin-top: 25px; text-align: center;">
<button
style="
padding: 8px 15px;
background-color: #F56040;
color: white;
border: none;
border-radius: 5px;
font-size: 14px;
width: 170px;
height: 40px;
cursor: pointer;
"
onclick="window.location.href='{{ path('boutique5sur5') }}'"
>
Voir toute la boutique
</button>
</li>
`;
$("#product-list").html(productList + boutiqueButton);
}
// Modifications au script existant
$(document).ready(function() {
// Fonctions pour ouvrir/fermer la sidebar
$("#openFavoritesSidebar").click(function() {
$("#favorites-sidebar").css("right", "0");
loadFavorites();
});
$("#close-favorites-btn").click(function() {
closeFavoritesSidebar();
});
// Fonction pour fermer la sidebar
window.closeFavoritesSidebar = function() {
$("#favorites-sidebar").css("right", "-500px");
};
// Tout sélectionner / Tout désélectionner
$("#select-all-btn").click(function() {
if (selectedFavorites.length === allFavorites.length) {
// Tout désélectionner
selectedFavorites = [];
$(".selection-overlay > div").css("background", "transparent").html("");
$(this).text("Tout sélectionner");
} else {
// Tout sélectionner
selectedFavorites = allFavorites.map(fav => fav.id);
$(".selection-overlay > div").css("background", "#F56040").html("<i class='bi bi-check' style='color:white;font-size:16px;'></i>");
$(this).text("Tout désélectionner");
}
// Mettre à jour le compteur
$("#selection-count").text(selectedFavorites.length);
// Mettre à jour l'interface produits
updateProductsView();
});
// Gestion des onglets
$(".sidebar-tab").click(function() {
$(".sidebar-tab").removeClass("active").css({
"color": "#666",
"border-bottom": "none"
});
$(this).addClass("active").css({
"color": "#F56040",
"border-bottom": "2px solid #F56040"
});
const tabId = $(this).attr("id");
$(".tab-content").hide();
if (tabId === "tab-photos") {
$("#photos-content").show();
} else if (tabId === "tab-products") {
$("#products-content").show();
}
});
// Modifier les fonctions existantes pour intercepter l'ajout/suppression de favoris
const originalAddFavoris = window.AddFavoris;
window.AddFavoris = function($id, $idSejour, $urlimg, $description) {
if (originalAddFavoris) {
originalAddFavoris($id, $idSejour, $urlimg, $description);
}
// Mettre à jour tous les compteurs de favoris
updateAllFavoriteCounters();
// Recharger les favoris si la sidebar est ouverte
if ($("#favorites-sidebar").css("right") === "0px") {
loadFavorites();
}
};
const originalSupprimerFavoris = window.supprimerFavoris;
window.supprimerFavoris = function($id, $idSejour) {
if (originalSupprimerFavoris) {
originalSupprimerFavoris($id, $idSejour);
}
// Mettre à jour tous les compteurs de favoris
updateAllFavoriteCounters();
// Recharger les favoris si la sidebar est ouverte
if ($("#favorites-sidebar").css("right") === "0px") {
// Retirer de la sélection si présent
selectedFavorites = selectedFavorites.filter(favId => favId != $id);
loadFavorites();
}
};
// Fermer en cliquant en dehors
$(document).click(function(event) {
if (!$(event.target).closest("#favorites-sidebar").length &&
!$(event.target).closest("#openFavoritesSidebar").length &&
$("#favorites-sidebar").css("right") === "0px") {
closeFavoritesSidebar();
}
});
});
// Initialisation de la sidebar
$(document).ready(function() {
// Fonctions pour ouvrir/fermer la sidebar
$("#openFavoritesSidebar").click(function() {
$("#favorites-sidebar").css("right", "0");
loadFavorites();
});
$("#close-favorites-btn").click(function() {
$("#favorites-sidebar").css("right", "-500px");
});
// Fermer en cliquant en dehors
$(document).click(function(event) {
if (!$(event.target).closest("#favorites-sidebar").length &&
!$(event.target).closest("#openFavoritesSidebar").length &&
$("#favorites-sidebar").css("right") === "0px") {
$("#favorites-sidebar").css("right", "-500px");
}
});
});
</script>
<script src="{{ asset('js/splide.min.js') }}" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const openBtn = document.getElementById('openFavoritesSidebar');
const sidebar = document.getElementById('favorites-sidebar');
const closeBtn = document.getElementById('favorites-close');
if (openBtn && sidebar) {
openBtn.addEventListener('click', () => {
if (sidebar && sidebar.classList) {
sidebar.classList.add('active');
}
});
}
if (closeBtn && sidebar) {
closeBtn.addEventListener('click', () => {
if (sidebar && sidebar.classList) {
sidebar.classList.remove('active');
}
});
}
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const filterBadges = document.querySelectorAll('.filter-badge');
filterBadges.forEach(badge => {
badge.addEventListener('click', function() {
// Désactiver tous les badges
filterBadges.forEach(b => {
if (b && b.classList) {
b.classList.remove('active');
}
});
// Activer le badge cliqué
if (this && this.classList) {
this.classList.add('active');
}
});
});
});
</script>
<script
type="text/javascript"
src="{{ asset('Accueil/js/jquery.magnific-popup.min.js') }}"
></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const prevButtons = document.querySelectorAll('.btn-prev-day');
const nextButtons = document.querySelectorAll('.btn-next-day');
const dateCards = document.querySelectorAll('.date-card.modern-card');
const collapseSections = document.querySelectorAll('.collapse');
// 🎯 MEDIA TOOLBAR CAPSULE - État global et logique (Senior UX)
(() => {
// État global de filtre
const MediaFilter = { current: 'all' };
// Compteurs dynamiques
const counters = { photo: 0, audio: 0, video: 0, fav: 0 };
const updateCounts = () => {
// Compter les médias dans le DOM
counters.photo = document.querySelectorAll('.photo-item, [data-media-type="photo"]').length;
counters.video = document.querySelectorAll('[data-media-type="video"]').length;
counters.audio = document.querySelectorAll('.audio-message-item[data-type="audio"]').length;
// NE PAS recalculer seulement les favoris - ils utilisent {{ nblikes }}
// Mettre à jour l'affichage (sauf favoris)
Object.entries(counters).forEach(([k, v]) => {
if (k !== 'fav') { // Garder seulement le compteur favoris intact
document.querySelectorAll(`.mtb-count[data-bind="${k}"]`).forEach(el => el.textContent = v);
}
});
};
// Simple fonction pour le filtre actuel
function setActive(filter) {
MediaFilter.current = filter;
// Exception : ne pas ajouter de classe active au bouton photos
if (filter === 'photos') {
const photosBtn = document.querySelector('.mtb-btn[data-filter="photos"]');
if (photosBtn) {
photosBtn.classList.remove('active', 'is-active');
}
}
}
// Fonction simplifiée qui mappe et appelle filterContent
function applyFilter(filter) {
// Mapper les filtres de la capsule vers les filtres existants si nécessaire
const filterMap = {
'all': 'toutVoir',
'photos': 'photos',
'audio': 'audio',
'videos': 'videos',
'favoris': 'favoris'
};
const mappedFilter = filterMap[filter] || filter;
// Pour le filtre "all", s'assurer que la vue des jours est restaurée
if (filter === 'all') {
const containerGallery = document.querySelector('.container--gallery');
if (containerGallery) {
containerGallery.style.display = 'block';
}
}
if (typeof window.filterContent === 'function') {
window.filterContent(mappedFilter);
} else {
console.error('window.filterContent n\'est pas disponible');
}
}
// Click handlers - exactement la même logique que stat-pill
document.addEventListener('click', (e) => {
const btn = e.target.closest('.mtb-btn');
if (!btn) return;
// Bouton fermer
if (btn.classList.contains('mtb-close')) {
document.querySelector('.media-toolbar')?.remove();
return;
}
const filter = btn.dataset.filter;
if (!filter) return;
// Debug spécifique pour le bouton favoris
if (filter === 'favoris') {
console.log('🔍 Bouton favoris cliqué - Debug:', {
btn: btn,
filter: filter,
hasFilterContent: typeof window.filterContent === 'function',
mesFavCount: document.getElementById('mesFavCount')?.textContent
});
// Protection : empêcher la décrémentation du compteur pour les clics de filtre
e.stopPropagation();
e.preventDefault();
// Appeler uniquement la fonction de filtrage
if (typeof window.filterContent === 'function') {
window.filterContent('favoris');
} else {
console.error('❌ window.filterContent n\'est pas disponible');
}
return; // Sortir de la fonction pour éviter le traitement normal
}
// Exception pour le bouton photos : ne pas modifier les états actifs
if (btn.dataset.filter === 'photos') {
// Pour le bouton photos, ne pas toucher aux autres boutons
// Juste exécuter l'action sans changer les états
} else {
// Pour les autres boutons : gérer les états actifs normalement
const mtbBtns = document.querySelectorAll('.mtb-btn[data-filter]');
for (let i = 0; i < mtbBtns.length; i++) {
const b = mtbBtns[i];
if (b && b.classList) {
if (b === btn) {
b.classList.add('active');
} else {
b.classList.remove('active');
}
}
}
}
// Déclencher le filtrage du contenu - même logique que stat-pill
console.log('mtb-btn click:', filter, 'filterContent type:', typeof window.filterContent);
if (typeof window.filterContent === 'function') {
window.filterContent(filter);
} else {
console.error('window.filterContent n\'est pas une fonction!');
}
});
// Expose un setter global
window.setMediaFilter = (filter) => {
// Pour le bouton photos : ne pas modifier les états
if (filter !== 'photos') {
setActive(filter);
}
applyFilter(filter);
};
// Mise à jour des compteurs quand les favoris changent
window.addEventListener('favorites:updated', updateCounts);
// Initialisation
setTimeout(() => {
updateCounts();
setActive('all');
}, 100);
// Mise à jour périodique des compteurs (sauf favoris qui reste fixe)
setInterval(updateCounts, 2000);
})();
function navigateToDay(index) {
if (index >= 0 && index < collapseSections.length) {
// Fermer tous les jours
collapseSections.forEach(s => {
if (s && s.classList) {
s.classList.remove('show');
}
});
dateCards.forEach(c => {
if (c && c.classList) {
c.classList.remove('active');
}
});
// Ouvrir le bon jour
const targetCollapse = collapseSections[index];
const targetCard = dateCards[index];
if (targetCollapse && targetCard) {
if (targetCollapse.classList) {
targetCollapse.classList.add('show');
}
if (targetCard.classList) {
targetCard.classList.add('active');
}
}
}
}
prevButtons.forEach(button => {
button.addEventListener('click', function () {
const targetIndex = parseInt(this.dataset.target, 10);
navigateToDay(targetIndex);
});
});
nextButtons.forEach(button => {
button.addEventListener('click', function () {
const targetIndex = parseInt(this.dataset.target, 10);
navigateToDay(targetIndex);
});
});
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const style = document.createElement('style');
style.textContent = `
.hidden {
display: none !important;
}
`;
document.head.appendChild(style);
});
document.addEventListener('DOMContentLoaded', function() {
// Get the gift button
const giftButton = document.querySelector('.gift-button');
if (giftButton) {
// Keep it clickable for the gift functionality
giftButton.style.pointerEvents = 'auto';
// Add click handler for gift action
giftButton.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
return false;
};
// Make sure hover still works
favoriteButton.addEventListener('mouseover', function() {
document.getElementById('purchase-alert').style.display = 'block';
});
// Keep any existing hover functionality
if (typeof showSelection === 'function') {
favoriteButton.onmouseover = showSelection;
}
}
// Make sure the purchase alert remains interactive
const purchaseAlert = document.getElementById('purchase-alert');
if (purchaseAlert) {
purchaseAlert.style.pointerEvents = 'auto';
}
});
// Sélection des éléments
const purchaseAlert = document.getElementById("purchase-alert");
const alertContent = document.getElementById("purchase-alert-content");
const likeCountLabel = document.getElementById("likeCount");
// Fonction pour obtenir le nombre actuel de favoris
function getCurrentFavoriteCount() {
const likeCountLabel = document.getElementById("likeCount");
return parseInt(likeCountLabel?.textContent.trim(), 10) || 0;
}
// Fonction pour mettre à jour le contenu de l'alerte
function updatePurchaseAlert(current) {
let remainingForAlbum = Math.max(0, 20 - current);
let remainingForPochette = Math.max(0, 12 - current);
let remainingForPack = Math.max(0, 12 - current);
const progressBar = (count, total, color) => `
<div style="margin: 5px 0;">
<div style="background-color: #e9ecef; border-radius: 5px; overflow: hidden; height: 8px;">
<div style="width: ${
(count / total) * 100
}%; background-color: ${color}; height: 100%;"></div>
</div>
<small style="font-size: 12px;">${count}/${total} photos</small>
</div>
`;
// Use Twig paths here:
const products = [
{
name: "Pochette photo (12 photos)",
required: 12,
remaining: remainingForPochette,
image: "/images/produit/PochettePhoto5sur5-2.jpg",
color: "#2196f3",
link: "{{ path('AjoutPochettePhotos_Favoris', {'nbr': 12}) }}",
},
{
name: "Pack numérique (20 photos)",
required: 20,
remaining: remainingForAlbum,
image: "/images/produit/photoNumerique.jpg",
color: "#4caf50",
link: "{{ path('PackPhotosNumerique_Favoris', {'nbr': 20}) }}",
}
].sort((a, b) => a.remaining - b.remaining);
const productList = products
.map((product) => {
const count = current;
const total = product.required;
const remaining = product.remaining;
return `
<li style="margin-bottom: 15px;">
<div style="display: flex; align-items: center; gap: 10px;">
<img
src="${product.image}"
alt="${product.name}"
style="width: 65px; height: 65px; border-radius: 5px; margin-top:-19px"
/>
<div style="flex: 1;">
<strong style="font-size: 14px;">${product.name}</strong>
${progressBar(count, total, product.color)}
${
remaining > 0
? `<small style="color: ${product.color}; font-size: 12px;">
Encore ${remaining} photos ❤️ pour compléter ${product.name.toLowerCase()}
</small>`
: `<button
style="
margin-top: 5px;
padding: 5px 8px;
background-color: ${product.color};
color: white;
border: none;
border-radius: 5px;
font-size: 12px;
cursor: pointer;
"
onclick="window.location.href='${product.link}'"
>
Commander
</button>`
}
</div>
</div>
</li>
`;
})
.join("");
const plusButton = `
<li style="margin-bottom: 15px; text-align: center;">
<button
style="
padding: 5px 8px;
background-color: #F56040;
color: white;
border: none;
border-radius: 5px;
font-size: 14px;
line-height: 1;
width: 150px;
height: 40px;
cursor: pointer;
"
onclick="window.location.href='{{ path('boutique5sur5') }}'"
>
Aller à la boutique
</button>
</li>
`;
if (current === 0) {
alertContent.innerHTML = `
<p style="font-size: 16px; font-weight: bold; color: #333;">
Vous n'avez pas encore de photos favorites !
</p>
<p style="margin-bottom: 20px; color: #555;">
Commencez à ajouter vos moments préférés pour profiter de nos offres.
</p>
<ul style="list-style-type: none; padding: 0;">${productList}${plusButton}</ul>
`;
} else {
alertContent.innerHTML = `
<p style="font-size: 16px; font-weight: bold; color: #333;">
Vous avez atteint ${current} photos favorites !
</p>
<p style="margin-bottom: 20px; color: #555;">
Profitez de nos offres spéciales :
</p>
<ul style="list-style-type: none; padding: 0;">${productList}${plusButton}</ul>
`;
}
if (purchaseAlert && purchaseAlert.classList) {
purchaseAlert.classList.remove("hidden");
}
clearTimeout(window.purchaseAlertTimeout);
window.purchaseAlertTimeout = setTimeout(() => {
if (!purchaseAlert.matches(":hover")) {
closePurchaseAlert();
}
}, 5000);
}
// Fonction pour fermer l'alerte
function closePurchaseAlert() {
if (purchaseAlert && purchaseAlert.classList) {
purchaseAlert.classList.add("hidden");
}
}
// Événement pour mettre à jour le contenu et afficher la popover dynamiquement au hover
document.querySelector(".gift-button").addEventListener("mouseover", () => {
const currentCount = getCurrentFavoriteCount();
updatePurchaseAlert(currentCount);
if (purchaseAlert && purchaseAlert.classList) {
purchaseAlert.classList.remove("cachee"); // Réaffiche la popover
}
});
// Nouvelles fonctions pour le bouton cadeau
function showGiftMessage() {
const tooltip = document.getElementById("giftTooltip");
if (tooltip) {
if (tooltip && tooltip.classList) {
tooltip.classList.add("show");
}
}
}
function hideGiftMessage() {
const tooltip = document.getElementById("giftTooltip");
if (tooltip) {
if (tooltip && tooltip.classList) {
tooltip.classList.remove("show");
}
}
}
function showSelection() {
document.getElementById("purchase-alert").style.display = "block";
}
function hideSelection() {
document.getElementById("selectionPopover").style.display = "none";
}
document.addEventListener("DOMContentLoaded", function () {
const container = document.querySelector(".date-container");
// Vérifie si le conteneur existe pour éviter les erreurs
if (container) {
container.scrollTo({
left: container.scrollWidth, // Scroll directement à la position maximale
behavior: "smooth", // Défilement fluide
});
}
});
document.addEventListener("DOMContentLoaded", function () {
const container = document.querySelector(".date-container");
const leftArrow = document.querySelector(".scroll-btn.left");
const rightArrow = document.querySelector(".scroll-btn.right");
// Fonction pour vérifier le débordement et activer/désactiver les flèches
function updateArrowsVisibility() {
const isOverflowing = container.scrollWidth > container.clientWidth; // Vérifie si débordement
leftArrow.style.display = isOverflowing ? "flex" : "none";
rightArrow.style.display = isOverflowing ? "flex" : "none";
}
// Fonction pour défiler
function scrollContainer(direction) {
container.scrollBy({
left: direction === "left" ? -200 : 200, // Défiler à gauche ou à droite
behavior: "smooth",
});
}
// Ajout des événements de clic pour les flèches
leftArrow.addEventListener("click", () => scrollContainer("left"));
rightArrow.addEventListener("click", () => scrollContainer("right"));
// ⚡ OPTIMISÉ: Debounce resize pour éviter surcharge
let resizeTimeout;
const debouncedResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(updateArrowsVisibility, 150);
};
updateArrowsVisibility();
window.addEventListener("resize", debouncedResize, { passive: true });
});
document.addEventListener("DOMContentLoaded", function () {
const container = document.querySelector(".date-container");
const leftBtn = document.querySelector(".scroll-btn.left");
const rightBtn = document.querySelector(".scroll-btn.right");
leftBtn.addEventListener("click", () => {
container.scrollBy({
left: -200, // Défile vers la gauche
behavior: "smooth",
});
});
rightBtn.addEventListener("click", () => {
container.scrollBy({
left: 200, // Défile vers la droite
behavior: "smooth",
});
});
});
document.addEventListener("DOMContentLoaded", function () {
// Sélectionnez tous les badges de filtre
const filterBadges = document.querySelectorAll(".filter-badge");
// Sélectionnez tous les éléments de la galerie
const galleryItems = document.querySelectorAll(".column");
// Sélectionnez tous les jours
const days = document.querySelectorAll(".collapse");
// Fonction pour réinitialiser les filtres
function resetFilters() {
// Réinitialisez tous les éléments de la galerie
galleryItems.forEach((item) => {
item.style.display = "block";
});
// Réinitialisez les états des badges
filterBadges.forEach((badge) => {
if (badge && badge.classList) {
badge.classList.remove("active");
}
});
}
// Ajoutez un gestionnaire d'événements pour chaque badge
filterBadges.forEach((badge) => {
badge.addEventListener("click", function () {
const filter = this.getAttribute("data-filter");
// Réinitialisez l'état actif pour tous les badges
filterBadges.forEach((btn) => {
if (btn && btn.classList) {
btn.classList.remove("active");
}
});
// Ajoutez l'état actif au badge cliqué
if (this && this.classList) {
this.classList.add("active");
}
// Affichez ou masquez les éléments de la galerie
galleryItems.forEach((item) => {
if (filter === "all") {
item.style.display = "block";
} else if (filter === "photo" && item.querySelector("img")) {
item.style.display = "block";
} else if (filter === "video" && item.querySelector("video")) {
item.style.display = "block";
} else if (filter === "audio" && item.classList && item.classList.contains("audio-message-item")) {
item.style.display = "block";
} else {
item.style.display = "none";
}
});
});
});
// Réinitialiser les filtres lors du changement de jour
days.forEach((day) => {
day.addEventListener("show.bs.collapse", function () {
resetFilters();
});
});
});
$(document).ready(function () {
let zoomCounter = 0; // Initialize zoom counter
let currentImageSrc = ""; // Track current image source
let lastClickPosition = { x: 50, y: 50 }; // Default to center of image
$(".container--gallery").magnificPopup({
delegate: "a",
type: "image",
mainClass: "mfp-with-zoom mfp-img-mobile",
image: {
verticalFit: true,
},
gallery: {
enabled: true,
tPrev: "Previous (Left arrow key)", // Alt text on left arrow
tNext: "Next (Right arrow key)", // Alt text on right arrow
tCounter: "%curr% of %total%", // Markup for "1 of 7" counter
},
zoom: {
enabled: true,
duration: 300,
opener: function (element) {
return element.find("img");
},
},
callbacks: {
open: function () {
// Get current image data from the link that was clicked
const currentLink = this.currItem.el;
const imageId =
currentLink
.closest(".photo-zoom")
.find(".heart-icon")
.data("id") || "";
const sejourId =
currentLink
.closest(".photo-zoom")
.find(".heart-icon")
.data("sejour-id") || "";
const imagePath =
currentLink
.closest(".photo-zoom")
.find(".heart-icon")
.data("path") || "";
const imageDesc =
currentLink
.closest(".photo-zoom")
.find(".heart-icon")
.data("description") || "";
const isFavorite = currentLink
.closest(".photo-zoom")
.find(".heart-icon i")
.hasClass("bi-heart-fill");
const favoriteIconClass = isFavorite ? "bi-heart-fill" : "bi-heart";
const favoriteIconColor = isFavorite ? "#f56040" : "white";
const favoriteTooltip = isFavorite
? "Retirer des favoris"
: "Ajouter aux favoris";
const zoomControls = `
<div class="mfp-zoom-controls">
<button class="zoom-btn zoom-out" title="Zoom Out"><i class="fa fa-search-minus"></i></button>
<button class="zoom-btn zoom-in" title="Zoom In"><i class="fa fa-search-plus"></i></button>
</div>
<div class="mfp-favorite">
<button class="favorite-btn"
data-id="${imageId}"
data-sejour-id="${sejourId}"
data-path="${imagePath}"
data-description="${imageDesc}"
title="${favoriteTooltip}">
<i class="bi ${favoriteIconClass}" style="color: ${favoriteIconColor}; text-shadow: 0px 0px 3px rgba(0,0,0,0.5);"></i>
</button>
</div>
<div class="mfp-counter"></div>
`;
$(".mfp-content").append(zoomControls);
initializeZoomControls();
initializeFavoriteButton();
const intervalId = setInterval(() => {
const newImageSrc = $(".mfp-img").attr("src");
if (newImageSrc !== currentImageSrc) {
currentImageSrc = newImageSrc;
zoomCounter = 0;
lastClickPosition = { x: 50, y: 50 }; // Reset to center
attachZoomHandler(); // Reattach zoom handler to new image
$(".mfp-img").css({
"transform-origin": `${lastClickPosition.x}% ${lastClickPosition.y}%`,
transform: `scale(1)`,
});
// Update favorite button for the new image
updateFavoriteButton();
initializeZoomControls();
updateCounter();
}
}, 100);
this.content.on("mfpClose", function () {
clearInterval(intervalId);
});
attachZoomHandler();
},
close: function () {
$(".mfp-zoom-controls").remove();
$(".mfp-favorite").remove();
$(".mfp-counter").remove();
zoomCounter = 0;
},
},
});
function attachZoomHandler() {
$(".mfp-img")
.off("click")
.on("click", function (event) {
event.stopPropagation(); // Prevent default navigation behavior
// Calculate click coordinates relative to the image
const imgOffset = $(this).offset();
const clickX = event.pageX - imgOffset.left;
const clickY = event.pageY - imgOffset.top;
const imgWidth = $(this).width();
const imgHeight = $(this).height();
// Calculate transform-origin based on click position
lastClickPosition = {
x: (clickX / imgWidth) * 100,
y: (clickY / imgHeight) * 100,
};
// Cycle through zoom levels: 1x, 1.5x, 2x
zoomCounter = (zoomCounter + 1) % 3;
const zoomLevels = [1, 1.5, 2];
const zoomLevel = zoomLevels[zoomCounter];
$(this).css({
"transform-origin": `${lastClickPosition.x}% ${lastClickPosition.y}%`,
transform: `scale(${zoomLevel})`,
});
updateZoomButtonState();
});
}
function initializeZoomControls() {
$(".mfp-zoom-controls .zoom-in")
.off("click")
.on("click", function (event) {
event.stopPropagation();
zoomCounter = (zoomCounter + 1) % 3;
const zoomLevels = [1, 1.5, 2];
const zoomLevel = zoomLevels[zoomCounter];
$(".mfp-img").css({
"transform-origin": `${lastClickPosition.x}% ${lastClickPosition.y}%`,
transform: `scale(${zoomLevel})`,
});
updateZoomButtonState();
});
$(".mfp-zoom-controls .zoom-out")
.off("click")
.on("click", function (event) {
event.stopPropagation();
if (zoomCounter > 0) {
zoomCounter -= 1;
const zoomLevels = [1, 1.5, 2];
const zoomLevel = zoomLevels[zoomCounter];
$(".mfp-img").css({
"transform-origin": `${lastClickPosition.x}% ${lastClickPosition.y}%`,
transform: `scale(${zoomLevel})`,
});
updateZoomButtonState();
} else {
$.magnificPopup.close();
}
});
}
function initializeFavoriteButton() {
$(".mfp-favorite .favorite-btn")
.off("click")
.on("click", function (event) {
event.stopPropagation();
const $this = $(this);
const imageId = $this.data("id");
const sejourId = $this.data("sejour-id");
// Toggle favorite status
const isFavorite = $this.find("i").hasClass("bi-heart-fill");
// Update the button appearance
if (isFavorite) {
$this
.find("i")
.removeClass("bi-heart-fill")
.addClass("bi-heart")
.css("color", "white");
$this.attr("title", "Ajouter aux favoris");
} else {
$this
.find("i")
.removeClass("bi-heart")
.addClass("bi-heart-fill")
.css("color", "#f56040");
$this.attr("title", "Retirer des favoris");
}
// Update the original heart icon in the gallery
const originalHeartIcon = $(
`.heart-icon[data-id="${imageId}"]`
).find("i");
if (isFavorite) {
originalHeartIcon
.removeClass("bi-heart-fill")
.addClass("bi-heart")
.css("color", "");
} else {
originalHeartIcon
.removeClass("bi-heart")
.addClass("bi-heart-fill")
.css("color", "#f56040");
}
// Make AJAX call to update favorite status in the backend using Parent routes
$.ajax({
url: isFavorite ? "/Parent/aSupprimerFav" : "/Parent/ajouterFav",
type: "POST",
data: {
id: imageId,
idSejour: sejourId,
},
success: function (response) {
// Optional: Show a success message or handle response
console.log("Favorite status updated", response);
},
error: function (error) {
console.error("Error updating favorite status", error);
// Revert the icon change on error
if (isFavorite) {
$this
.find("i")
.removeClass("bi-heart")
.addClass("bi-heart-fill")
.css("color", "#f56040");
originalHeartIcon
.removeClass("bi-heart")
.addClass("bi-heart-fill")
.css("color", "#f56040");
} else {
$this
.find("i")
.removeClass("bi-heart-fill")
.addClass("bi-heart")
.css("color", "white");
originalHeartIcon
.removeClass("bi-heart-fill")
.addClass("bi-heart")
.css("color", "");
}
},
});
});
}
function updateFavoriteButton() {
// Get current image data from the current slide
const currentSlide = $.magnificPopup.instance.currItem.el;
const photoZoom = currentSlide.closest(".photo-zoom");
if (photoZoom.length) {
const heartIcon = photoZoom.find(".heart-icon");
const imageId = heartIcon.data("id") || "";
const sejourId = heartIcon.data("sejour-id") || "";
const imagePath = heartIcon.data("path") || "";
const imageDesc = heartIcon.data("description") || "";
const isFavorite = heartIcon.find("i").hasClass("bi-heart-fill");
const favoriteIconClass = isFavorite ? "bi-heart-fill" : "bi-heart";
const favoriteIconColor = isFavorite ? "#f56040" : "white";
const favoriteTooltip = isFavorite
? "Retirer des favoris"
: "Ajouter aux favoris";
// Update the favorite button
const $favoriteBtn = $(".mfp-favorite .favorite-btn");
$favoriteBtn.data("id", imageId);
$favoriteBtn.data("sejour-id", sejourId);
$favoriteBtn.data("path", imagePath);
$favoriteBtn.data("description", imageDesc);
$favoriteBtn.attr("title", favoriteTooltip);
$favoriteBtn
.find("i")
.removeClass("bi-heart bi-heart-fill")
.addClass(favoriteIconClass)
.css("color", favoriteIconColor);
}
}
function updateZoomButtonState() {
const zoomLevels = [1, 1.5, 2];
const currentZoom = zoomLevels[zoomCounter];
$(".zoom-in").prop("disabled", currentZoom === 2);
$(".zoom-out").prop("disabled", currentZoom === 1);
}
function updateCounter() {
const counterText = $(".mfp-counter")
.closest(".mfp-content")
.find(".mfp-counter")
.text();
const matches = counterText.match(/(\d+) of (\d+)/);
if (matches) {
const currentIndex = matches[1];
const totalImages = matches[2];
$(".mfp-counter").text(`${currentIndex} of ${totalImages}`);
}
}
// Add CSS for the favorite button and rounded image corners
$("<style>")
.prop("type", "text/css")
.html(
`
.mfp-favorite {
position: absolute;
top: 15px;
left: 15px;
z-index: 1046;
}
.favorite-btn {
background: transparent;
border: none;
font-size: 24px;
padding: 5px;
cursor: pointer;
outline: none;
}
.favorite-btn i {
transition: all 0.3s ease;
}
.favorite-btn:hover i {
transform: scale(1.2);
}
/* Rounded corners for zoomed images */
.mfp-img {
border-radius: 8px;
}
/* Make sure the container doesn't clip the rounded corners */
.mfp-figure:after {
border-radius: 8px;
}
`
)
.appendTo("head");
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const openBtn = document.getElementById('openFavoritesBtn');
const closeBtn = document.getElementById('closeSidebarBtn');
const sidebar = document.getElementById('favoritesSidebar');
const tbody = document.querySelector('#favoritesTable tbody');
openBtn.addEventListener('click', async () => {
try {
const response = await fetch('/Parent/mes-favoris', {
headers: {
'Accept': 'application/json'
}
});
const result = await response.json();
if (!result.success || !Array.isArray(result.data)) {
alert('Erreur lors du chargement des favoris.');
return;
}
tbody.innerHTML = '';
result.data.forEach((fav, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${index + 1}</td>
<td><img src="${fav.path}" alt="favori"></td>
<td>${fav.descreption || '—'}</td>
<td>${fav.created_at}</td>
`;
tbody.appendChild(row);
});
sidebar.classList.add('active');
} catch (e) {
console.error('Erreur réseau:', e);
alert('Impossible de charger les favoris.');
}
});
closeBtn.addEventListener('click', () => {
sidebar.classList.remove('active');
});
});
</script>
<script>
// Fonction pour vérifier et afficher l'alerte
function checkFavoritesAlert() {
const currentCount = window.favoriteCount || 0;
if (currentCount >= 10) {
const purchaseAlert = document.getElementById('purchase-alert');
if (purchaseAlert) {
purchaseAlert.style.display = 'block'; // Affiche l'alerte
}
} else {
purchaseAlert.style.display = 'none'; // Cache l'alerte si le nombre est réduit
}
}
document.addEventListener('DOMContentLoaded', () => {
const favoriteCount = {{ nblikes }};
updateCardContent(favoriteCount);
});
function updateCardContent(favoriteCount) {
const card = document.getElementById('dynamic-card');
const cardContent = document.getElementById('dynamic-card-content');
let produits = [];
if (favoriteCount >= 20) {
produits.push({
titre: "Album débloqué !",
bouton: "Commander",
image: "/images/produit/Album5sur5-3.jpg",
lien: "{{ path('EditionAlbum') }}"
});
}
if (favoriteCount >= 12) {
produits.push({
titre: "Pochette débloquée !",
bouton: "Commander",
image: "/images/produit/PochettePhoto5sur5-2.jpg",
lien: "{{ path('AjoutPochettePhotos_Favoris', {'nbr': 12}) }}"
});
}
if (favoriteCount >= 5) {
produits.push({
titre: "Pack numérique débloqué !",
bouton: "Commander",
image: "/images/produit/photoNumerique.jpg",
lien: "{{ path('PackPhotosNumerique_Favoris', {'nbr': 15}) }}"
});
}
if (produits.length === 0) {
cardContent.innerHTML = `
<div style="position: relative; width: 100%; height: 140px; border-radius: 15px; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.1);">
<div style="background-image: url('/images/produit/CoffretCadeau5sur5-2.jpg'); background-size: cover; background-position: center; width: 100%; height: 100%;">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; padding: 10px;">
<div style="
margin-top: 35px;
color: white;
font-size: 15px;
font-weight: bold;
text-align: center;
line-height: 1;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);">
Ajoutez des favoris ❤️<br><span style="font-size: 16px; font-weight: normal;">pour débloquer un souvenir à offrir 🎁</span>
</div>
</div>
</div>
</div>
`;
return;
}
cardContent.innerHTML = `
<div class="splide" id="dynamicSplide">
<div class="splide__track">
<ul class="splide__list">
${produits.map(produit => `
<li class="splide__slide" style="position: relative;">
<img src="${produit.image}" alt="${produit.titre}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 8px;">
<div style="position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.1); color: white; padding: 10px; text-align: center; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;">
<div style="font-weight: bold; font-size: 14px;">${produit.titre}</div>
<button style="margin-top: 5px; padding: 5px 8px; background-color: #F56040; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer;" onclick="window.location.href='${produit.lien}'">
${produit.bouton}
</button>
</div>
</li>
`).join('')}
</ul>
</div>
</div>
`;
// Monte le carrousel
new Splide('#dynamicSplide', {
type: 'loop',
arrows: true,
pagination: false,
autoplay: true,
interval: 4000,
speed: 800,
}).mount();
}
function supprimerFavoris($id, $idSejour) {
// Vider l'élément coeur pour ce favori
const coeurElement = $('#coeur' + $id);
coeurElement.empty();
// Ajout d'une animation sur le bouton cadeau
const giftButton = document.querySelector('.gift-button');
if (giftButton) {
giftButton.classList.add('active');
// Retirer l'animation après qu'elle soit jouée
setTimeout(() => {
giftButton.classList.remove('active');
}, 600); // La durée doit correspondre à celle de l'animation
}
// Mise à jour de l'icône coeur
const clas = $('.IconImag6').hasClass('active') ? "IconDelete IconDeletesix" : "IconDelete";
coeurElement.html(
`<i class="bi bi-heart ${clas}" ></i>`
);
// Mettre à jour le compteur des favoris
const likeCountLabel = document.getElementById('likeCount');
const likeMesFavLabel = document.getElementById('mesFavCount');
let newCount = 0;
if (likeCountLabel) {
// Utiliser la fonction utilitaire robuste
let currentCount = window.getFavoriteCount();
newCount = Math.max(0, currentCount - 1); // Empêche le compteur d'aller en dessous de 0
window.setFavoriteCount(newCount);
// Mettre à jour la valeur dans l'input hidden
const nbFavCurrentInput = $('#nbFavCurrent');
if (nbFavCurrentInput.length) {
nbFavCurrentInput.val(newCount);
}
}
// Synchroniser mesFavCount avec likeCount
if (likeMesFavLabel) {
if (likeCountLabel) {
// Utiliser la valeur de likeCount pour synchroniser
likeMesFavLabel.textContent = newCount;
} else {
// Si likeCount n'existe pas, décrémenter directement mesFavCount
let currentFavCount = parseInt(likeMesFavLabel.textContent.trim(), 10) || 0;
newCount = Math.max(0, currentFavCount - 1);
likeMesFavLabel.textContent = newCount;
}
}
// Mettre à jour l'alerte immédiatement
setTimeout(() => {
const finalCount = getCurrentFavoriteCount();
if (typeof updatePurchaseAlert === 'function') {
updatePurchaseAlert(finalCount);
}
}, 50);
// Préparation des données pour l'Ajax
const $_data = { 'id': $id, 'idSejour': $idSejour };
// Appel Ajax pour supprimer le favori
$.ajax({
type: "POST",
url: "{{ path('Supprimer_fav') }}",
data: $_data,
success: function () {
// Réactiver les icônes après succès
$('.IconDelete').each(function () {
$(this).css('pointer-events', '');
});
// Mettre à jour l'alerte avec le nouveau nombre
const finalCount = getCurrentFavoriteCount();
if (typeof updatePurchaseAlert === 'function') {
updatePurchaseAlert(finalCount);
}
},
error: function (xhr, status, error) {
console.error('Erreur lors de la suppression du favori :', error);
}
});
}
function AddFavoris($id, $idSejour, $urlimg, $description) {
// Update heart icon
$('#coeur' + $id).empty();
var clas = $('.IconImag6').hasClass('active') ? "IconDelete IconDeletesix" : "IconDelete";
$('#coeur' + $id).html("<i class=\"bi bi-heart-fill favSelect " + clas + "\" )\"></i>");
// Update counters UNIQUEMENT
const likeCountLabel = document.getElementById('likeCount');
const likeMesFavLabel = document.getElementById('mesFavCount');
const giftCountLabel = document.getElementById('giftCount');
if (likeCountLabel) {
// Utiliser la fonction utilitaire robuste
let currentCount = window.getFavoriteCount();
currentCount++;
window.setFavoriteCount(currentCount);
// Mettre à jour le compteur des favoris dans le titre
if (likeMesFavLabel) {
likeMesFavLabel.textContent = currentCount;
}
if (giftCountLabel) {
giftCountLabel.textContent = currentCount;
}
// Mettre à jour l'input hidden
const nbFavCurrentInput = document.getElementById('nbFavCurrent');
if (nbFavCurrentInput) {
nbFavCurrentInput.value = currentCount;
}
}
// Update other counters
var $total = parseInt($("#totalLike").html()) + 1;
$("#totalLike").html($total);
$("#totalLikeTitle").html($total);
$("#totalLikeMobile").html($total);
// Add gift button animation
const giftButton = document.querySelector('.gift-button');
if (giftButton) {
giftButton.classList.add('active');
setTimeout(() => {
giftButton.classList.remove('active');
}, 600);
}
var $data = { 'id': $id, 'idSejour': $idSejour };
$.ajax({
type: "POST",
url: "{{ path('Ajouter_fav') }}",
data: $data,
success: function () {
$('.IconDelete').each(function () {
$(this).css('pointer-events', '');
});
if ($description === undefined) {
$description = ''; // Set it to an empty string
}
$('.rowMaselection').append(
'<div class="column" id="column-' + $id + '">'+
'<a style="position: relative;" title="Enlever de ma sélection" onclick="supprimerFavoris(' + $id + ',' + $idSejour + ')" class="iconeSuppImg"><i class="bi bi-x" style="font-size:17px;cursor:pointer;color:#d30909;float:right;margin-top:-3%;margin-right:2%"></i></a>'+
'<a class="photo-zoom">'+
'<img data-idAtach="'+$id+'" id="'+$idSejour+'" src="'+$urlimg+'"></a>'+
($description ? '<h4 id="commint" class="titleHeadPhoto">'+$description+'</h4>' : '')+ // Only add the <h4> if $description is not empty
'</div>'
);
// Directly update nbLikes count in the header
var currentNbLikes = parseInt($('#favoris-link-Accueilpayment .nbrpanier').text());
var newNbLikes = currentNbLikes + 1;
$('#favoris-link-Accueilpayment .nbrpanier').text(newNbLikes);
// Mettre à jour l'alerte avec le nouveau nombre
const finalCount = getCurrentFavoriteCount();
if (typeof updatePurchaseAlert === 'function') {
updatePurchaseAlert(finalCount);
}
},
error: function (xhr, status, error) {
console.error('Error:', error);
}
});
}
$(document).on('click', '.bi-heart, .bi-heart-fill', function () {
const heartIcon = $(this);
const heartContainer = heartIcon.closest('.heart-icon');
// Extract data attributes
const attachmentId = heartContainer.data('id');
const sejourId = heartContainer.data('sejour-id');
const path = heartContainer.data('path');
const description = heartContainer.data('description');
const isFavorite = heartIcon.hasClass('bi-heart-fill');
if (isFavorite) {
// Remove from favorites
supprimerFavoris(attachmentId, sejourId);
} else {
// Add to favorites
AddFavoris(attachmentId, sejourId, path, description);
}
// Update UI components after the action (sans double comptage)
setTimeout(function() {
const likeCountLabel = document.getElementById('likeCount');
if (likeCountLabel) {
const currentCount = parseInt(likeCountLabel.textContent, 10) || 0;
// Update UI components seulement
updateCardContent(currentCount);
updateFavoritesSidebar();
$("#close-favorites-btn").click();
}
}, 50);
});
// Ajoutez les événements sur les icônes de cœur
document.querySelectorAll('.IconDelete').forEach((icon) => {
icon.addEventListener('click', (event) => {
const isFavorite = icon && icon.classList && icon.classList.contains('bi-heart-fill');
if (isFavorite) {
removeFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart-fill');
icon.classList.add('bi-heart');
}
} else {
addFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart');
icon.classList.add('bi-heart-fill');
}
}
});
});
// Vérifie l'état initial
checkFavoritesAlert();
// ⚡ OPTIMISÉ: Réduction du délai d'initialisation
</script>
<!-- Initialisation -->
<script>
// ⚡ OPTIMISATION: Différer l'initialisation d'AOS pour ne pas bloquer le chargement
setTimeout(function() {
AOS.init({
duration: 800,
easing: "ease-in-out"
});
}, 100);
// 🎯 DAY FILTER DROPDOWN LOGIC (Senior UX)
document.addEventListener('DOMContentLoaded', function() {
initializeDayFilters();
// Initialiser le compteur audio avec la valeur correcte
setTimeout(function() {
updateAudioButtonState('days');
}, 500);
});
function initializeDayFilters() {
const dropdowns = document.querySelectorAll('.day-filter-dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('.filter-toggle');
const menu = dropdown.querySelector('.filter-dropdown-menu');
const options = dropdown.querySelectorAll('.filter-option');
const dayIndex = dropdown.dataset.dayIndex;
const dayContainer = document.getElementById(`demP${dayIndex}`);
if (!toggle || !menu || !dayContainer) return;
// Calculer et mettre à jour les compteurs initiaux
updateDayFilterCounts(dropdown, dayContainer);
// Toggle dropdown
toggle.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = dropdown.classList.contains('open');
// Fermer tous les autres dropdowns
document.querySelectorAll('.day-filter-dropdown.open').forEach(d => {
if (d !== dropdown) d.classList.remove('open');
});
// Toggle le dropdown actuel
dropdown.classList.toggle('open', !isOpen);
});
// Gérer les clics sur les options
options.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const filter = option.dataset.filter;
// Mettre à jour l'état actif
options.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
// Appliquer le filtre
applyDayFilter(dayContainer, filter);
// Fermer le dropdown
dropdown.classList.remove('open');
});
});
});
// Fermer les dropdowns en cliquant ailleurs
document.addEventListener('click', () => {
document.querySelectorAll('.day-filter-dropdown.open').forEach(dropdown => {
dropdown.classList.remove('open');
});
});
}
function updateDayFilterCounts(dropdown, dayContainer) {
const photoItems = dayContainer.querySelectorAll('[data-type="photo"]');
const videoItems = dayContainer.querySelectorAll('[data-type="video"]');
// Compter les messages audio individuels plutôt que les conteneurs
const audioMessageItems = dayContainer.querySelectorAll('.audio-message-item[data-type="audio"]');
const audioContainers = dayContainer.querySelectorAll('.audio-messages-container[data-type="audio"]');
const audioRestricted = dayContainer.querySelectorAll('.audio-messages-restricted[data-type="audio"]');
const allItems = dayContainer.querySelectorAll('[data-type]');
const countAll = dropdown.querySelector('[data-count-all]');
const countPhoto = dropdown.querySelector('[data-count-photo]');
const countVideo = dropdown.querySelector('[data-count-video]');
const countAudio = dropdown.querySelector('[data-count-audio]');
// Compter les messages audio individuels + conteneurs + sections restreintes
const totalAudio = audioMessageItems.length + audioContainers.length + audioRestricted.length;
if (countAll) countAll.textContent = allItems.length;
if (countPhoto) countPhoto.textContent = photoItems.length;
if (countVideo) countVideo.textContent = videoItems.length;
if (countAudio) countAudio.textContent = totalAudio;
// Masquer les options sans contenu
const photoOption = dropdown.querySelector('[data-filter="photo"]');
const videoOption = dropdown.querySelector('[data-filter="video"]');
const audioOption = dropdown.querySelector('[data-filter="audio"]');
if (photoOption) photoOption.style.display = photoItems.length > 0 ? 'flex' : 'none';
if (videoOption) videoOption.style.display = videoItems.length > 0 ? 'flex' : 'none';
if (audioOption) audioOption.style.display = totalAudio > 0 ? 'flex' : 'none';
}
function applyDayFilter(dayContainer, filter) {
const items = dayContainer.querySelectorAll('[data-type]');
items.forEach(item => {
if (filter === 'all' || item.dataset.type === filter) {
item.style.display = '';
item.classList.remove('filtered-out');
} else {
item.style.display = 'none';
item.classList.add('filtered-out');
}
});
// Animation fluide pour les éléments visibles
requestAnimationFrame(() => {
const visibleItems = dayContainer.querySelectorAll('[data-type]:not(.filtered-out)');
visibleItems.forEach((item, index) => {
item.style.animation = `fadeInUp 0.3s ease forwards ${index * 0.05}s`;
});
});
}
// Animation CSS pour fadeInUp
if (!document.querySelector('#dayFilterAnimations')) {
const style = document.createElement('style');
style.id = 'dayFilterAnimations';
style.textContent = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
document.head.appendChild(style);
}
document.addEventListener("DOMContentLoaded", function () {
const dateCards = document.querySelectorAll(".date-card");
const sections = document.querySelectorAll(".collapse");
dateCards.forEach((card) => {
card.addEventListener("click", function () {
// Supprimer les classes actives des autres cartes et sections
dateCards.forEach((c) => c.classList.remove("active"));
sections.forEach((s) => s.classList.remove("show"));
// Ajouter la classe active à la carte cliquée
this.classList.add("active");
// Récupérer la cible et afficher la bonne section
const targetId = this.getAttribute("data-bs-target");
const targetSection = document.querySelector(targetId);
if (targetSection) {
targetSection.classList.add("show");
}
});
});
});
document.addEventListener("DOMContentLoaded", function () {
// Initialisation du carrousel Splide
var splide = new Splide("#imageSlider", {
type: "loop",
perPage: 1,
autoplay: true,
interval: 6000,
pauseOnHover: false,
pauseOnFocus: false,
pagination: false, // Désactive la pagination
arrows: false,
});
splide.mount();
// Fonction pour faire défiler automatiquement vers la section suivante avec un ajustement personnalisé de la hauteur
function scrollToNextSection() {
const targetSection = document.getElementById("scrollTarget");
if (targetSection) {
const targetPosition =
targetSection.getBoundingClientRect().top + window.scrollY; // Position de la section
const adjustedPosition = targetPosition - 50; // Réduit la hauteur du scroll de 150px (ajustez selon vos besoins)
// Scroll vers la position ajustée
window.scrollTo({
top: adjustedPosition,
behavior: "smooth",
});
}
}
// Démarrer le timer pour le scroll automatique après 10 secondes
setTimeout(scrollToNextSection, 5000); // 10 secondes
});
</script>
<script>
const giftButton = document.querySelector('.gift-button');
if (giftButton) {
giftButton.addEventListener('click', () => {
// Ajouter la classe 'active' pour déclencher l'éclat
giftButton.classList.add('active');
// Retirer l'animation après qu'elle soit jouée
setTimeout(() => {
giftButton.classList.remove('active');
}, 600); // La durée doit correspondre à celle de l'animation
// Optionnel : rediriger vers la page de commande
// window.location.href = '/commande';
});
}
//const HeartAddButton = document.querySelector('.IconDelete');
$(".IconDelete").on('click', () => {
// Ajouter la classe 'active' pour déclencher l'éclat
favoriteButton.classList.add('active');
// Retirer l'animation après qu'elle soit jouée
setTimeout(() => {
favoriteButton.classList.remove('active');
}, 600); // La durée doit correspondre à celle de l'animation
});
$(document).ready(function() {
// Attach click event to collapse triggers
const lastCard = $('.date-card.modern-card.active');
const lastTargetId = lastCard.attr('data-bs-target');
if (lastTargetId) {
$(lastTargetId).collapse('show'); // Expand the last collapse section
LoadImagesCloud($(lastTargetId)); // Load images for the last day
}
$('[data-bs-toggle="collapse"]').on('click', function() {
var targetId = $(this).attr('data-bs-target'); // Get the target ID
$('.date-card.modern-card').removeClass('active'); // Remove 'active' class from all cards
$(this).addClass('active'); // Add 'active' class to the clicked card
LoadImagesCloud($(targetId)); // Ensure this function works as expected
// Hide all other collapses except the one clicked
$('[data-bs-target]').each(function() {
var currentTargetId = $(this).attr('data-bs-target');
// If the current collapse is not the one clicked, hide it
if (currentTargetId !== targetId) {
$(currentTargetId).collapse('hide');
//$('[data-bs-toggle="collapse"]').removeClass('active'); // Remove active class from all cards
//Modifier leurs style en non active aussi
}
});
});
});
$(document).ready(function () {
{% if app.session.get("paymentmoniteco") %}
{% if app.session.get("paymentmoniteco") == "succses" %}
Swal.fire({
icon: 'success',
title: ' succès ',
text: 'votre commande est validée'
});
{% endif %}
{% endif %}
if ($total1 > 0) {
$('.iconeFleche').first().click();
// $([document.documentElement, document.body]).animate({
// scrollTop: $('.iconeFleche').last().offset().top
// }, );
}
else {
$(window).scrollTop(0);
}
var slider = $('.responsive').slick({
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 4000,
pauseOnFocus: false,
pauseOnHover: false,
draggable: false,
fade: true
});
$('.responsive').css('display', 'block');
$('.namePRD').css('display', 'block');
var currSlide = 0;
var nextSlide = 0;
slider.on('afterChange', function (event, slick, currentSlide) {
console.log(typeof ($('.slick-active .slick-current').find('.imgproduit2')) != "undefined");
if (typeof ($('.slick-active .slick-current').find('.imgproduit2')) != "undefined") {
setTimeout(function () {
$('.slick-active .imgproduit1').removeClass('animated fadeIn');
$('.slick-active .imgproduit1').addClass('animated fadeOut');
$('.slick-active .imgproduit1').css('display', 'none');
$('.slick-active .imgproduit2').css('display', 'block');
$('.slick-active .imgproduit2').removeClass('animated fadeOut');
$('.slick-active .imgproduit2').addClass('animated fadeIn');
}, 2000);
}
});
slider.on('beforeChange', function (event, slick, currentSlide, nextSlide) {
currSlide = currentSlide;
$('.imgproduit2').each(function () {
$(this).removeClass('animated fadeIn');
$(this).addClass('animated fadeOut');
$(this).css('display', 'none');
});
$('.imgproduit1').each(function () {
$(this).css('display', 'block');
$(this).removeClass('animated fadeOut');
$(this).addClass('animated fadeIn');
});
});
$('.columnPub').each(function () {
$(this).slick({
infinite: true,
speed: 50,
fade: true,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
pauseOnFocus: false,
pauseOnHover: false,
draggable: false
});
$(this).css('display', 'block');
});
$("#offrePack").click();
{%if app.user.showpubprod != 'false' %}
$('#btnPubProd').click();
$('.modal-backdrop').css('background-color', 'rgba(0, 0, 0, 0.2)');
{% endif %}
});
$("#closeImage").click(function () {
$('#myModalImage').css('display', "none");
});
$.ajax({
type: "POST",
url: "{{ path("delateSession_parent") }}",
success: function () { }
});
function afficheDiv(elem) {
$('.nav-link').each(function () {
$(this).removeClass('active');
});
elem.addClass('active');
if (elem.attr('id') === "esphoto" || elem.attr('id') === "esphotoMobile") {
$("#espacphoto").show();
$("#espacemessage").hide();
$("#espaceMa_selection").hide();
pageMenu = 'MonSejour'
$(this).addClass('active');
$('#imageActifphoto').css('display', 'block');
$('#imagenoActifphoto').css('display', 'none');
$('#VocalActivee').css('display', 'none');
$('#noActifVocal').css('display', 'block');
}
if (elem.attr('id') === "esmessage" || elem.attr('id') === "esmessageMobile") {
$("#espacphoto").hide();
$("#espaceMa_selection").hide();
$("#espacemessage").show();
pageMenu = 'BoiteVocale'
$("#espaceMa_selection").hide();
$(this).addClass('active');
$('#imageActifphoto').css('display', 'none');
$('#imagenoActifphoto').css('display', 'block');
$('#VocalActivee').css('display', 'block');
$('#noActifVocal').css('display', 'none');
}
if (elem.attr('id') === "esselection" || elem.attr('id') === "esselectionMobile") {
$("#espacphoto").hide();
$("#espacemessage").hide();
$("#espaceMa_selection").show();
$(homeNavmob).removeClass('bi bi-house-door-fill');
$(homeNavmob).addClass('bi bi-house-door');
$(micromob).removeClass('bi bi-mic-fill');
$(micromob).addClass('bi bi-mic');
$(selecNavmob).removeClass('bi bi-heart');
$(selecNavmob).addClass('bi bi-heart-fill');
}
}
function LoadImagesCloud($element) {
$element.find('.photo-zoom img').each(function ($this) {
if ($(this).attr('data-src') != $(this).attr('src')) {
$(this).attr('src', $(this).attr('data-src'));
}
});
}
</script>
<script>
// ⚡ OPTIMISATION: Regroupement des initialisations jQuery
$(document).ready(function () {
// Modal PubProd
$("#PubProd").on("hidden.bs.modal", function () {
$(document).trigger("modalClosed");
});
// NoShow checkbox
$("#noShow").on("change", function () {
if ($(this).is(":checked")) {
$.ajax({
url: "/Parent/showpub",
type: "POST",
dataType: "json",
success: function (response) {
if (response.status === "success") {
console.log("User showpubprod updated successfully.");
} else {
console.log("Error:", response.message);
}
},
error: function (xhr, status, error) {
console.log("AJAX Error:", error);
},
});
}
});
});
</script>
</div>
<!-- Script pour la sidebar des favoris -->
<script>
// ⚡ OPTIMISATION: Utiliser requestIdleCallback pour ne pas bloquer le thread principal
// Charger les favoris
function loadFavorites() {
$.ajax({
url: "/Parent/mes-favoris",
type: "GET",
dataType: "json",
beforeSend: function() {
$("#favorites-grid").html("<div style='text-align:center'>Chargement...</div>");
},
success: function(data) {
$("#favorites-grid").empty();
if (data.data && data.data.length > 0) {
$("#favorites-empty-state").hide();
$.each(data.data, function(i, fav) {
var item = $("<div class='favorite-item'></div>");
var img = $("<img>").attr("src", fav.path).attr("alt", fav.descreption || "Photo favorite");
var overlay = $("<div class='favorite-overlay'></div>");
btn.click(function(e) {
e.preventDefault();
e.stopPropagation();
removeFavorite(fav.id);
});
overlay.append(btn);
item.append(img).append(overlay);
$("#favorites-grid").append(item);
});
$("#favorites-counter").text(data.data.length);
var percentage = (data.data.length / 10) * 100;
$("#favorites-progress").css("width", percentage + "%");
} else {
$("#favorites-empty-state").show();
$("#favorites-counter").text("0");
$("#favorites-progress").css("width", "0%");
}
},
error: function() {
$("#favorites-grid").html("<div style='color:red;text-align:center'>Erreur de chargement</div>");
}
});
}
// Supprimer un favori
function removeFavorite(id) {
$.ajax({
url: "/Parent/remove-favorite/" + id,
type: "POST",
success: function() {
loadFavorites();
// Mettre à jour tous les compteurs de favoris
updateAllFavoriteCounters();
},
error: function() {
alert("Erreur lors de la suppression du favori");
}
});
}
});
</script>
<!-- === E-COMMERCE SIDEBAR JAVASCRIPT === -->
<script>
// Configuration UX_VARIANT pour A/B testing
let UX_VARIANT = 'EMOTION'; // ou 'URGENCY'
// Variables globales e-commerce
let sejourEndDate = null; // À définir avec la vraie date de fin du séjour
// Fonction utilitaire robuste pour gérer les compteurs de favoris
window.getFavoriteCount = function getFavoriteCount() {
const likeCountInput = document.getElementById('likeCount');
if (likeCountInput) {
// Priorité à la valeur de l'input
const value = likeCountInput.value || likeCountInput.textContent || 0;
return parseInt(value, 10) || 0;
}
// Fallback sur giftCount
const giftCount = document.getElementById('giftCount');
if (giftCount && giftCount.textContent) {
return parseInt(giftCount.textContent.trim(), 10) || 0;
}
return 0;
};
window.setFavoriteCount = function setFavoriteCount(count) {
const likeCountInput = document.getElementById('likeCount');
if (likeCountInput) {
// Mettre à jour les deux propriétés pour être sûr
likeCountInput.value = count;
likeCountInput.textContent = count;
}
// Mettre à jour les autres compteurs
const giftCount = document.getElementById('giftCount');
if (giftCount) {
giftCount.textContent = count;
}
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
mesFavCount.textContent = count;
}
// Mettre à jour l'input hidden
const nbFavCurrentInput = document.getElementById('nbFavCurrent');
if (nbFavCurrentInput) {
nbFavCurrentInput.value = count;
}
};
// Fonction pour définir la date de fin du séjour
window.setSejourEndDate = function setSejourEndDate(dateString) {
sejourEndDate = dateString;
console.log('🎯 Sejour end date set to:', sejourEndDate);
// Mettre à jour le countdown si le sidebar est ouvert
updateCountdownTimer();
};
// Fonctions d'ouverture/fermeture du sidebar
window.openEcommerceSidebar = function openEcommerceSidebar() {
const sidebar = document.getElementById('ecommerce-sidebar');
if (!sidebar) {
console.error('❌ E-commerce sidebar element not found!');
return false;
}
// Ajouter la classe active
sidebar.classList.add('active');
// Mettre à jour le contenu avec le nombre actuel de favoris
let favoriteCount = window.getFavoriteCount();
console.log('🎯 Opening sidebar with favoriteCount:', favoriteCount);
// Mettre à jour le contenu du sidebar
if (typeof window.updateEcommerceSidebarContent === 'function') {
window.updateEcommerceSidebarContent(favoriteCount);
}
return true;
};
window.closeEcommerceSidebar = function closeEcommerceSidebar() {
const sidebar = document.getElementById('ecommerce-sidebar');
if (sidebar) {
sidebar.classList.remove('active');
console.log('🎯 E-commerce sidebar closed');
} else {
console.error('❌ E-commerce sidebar element not found!');
}
};
// Fonction pour mettre à jour le contenu du sidebar
window.updateEcommerceSidebarContent = function updateEcommerceSidebarContent(favoriteCount) {
const title = document.getElementById('ecommerce-title');
const subtitle = document.getElementById('ecommerce-subtitle');
const albumCount = document.getElementById('album-count');
const digitalCount = document.getElementById('digital-count');
const printsCount = document.getElementById('prints-count');
const printsCard = document.getElementById('prints-card');
const productsContainer = document.getElementById('products-container');
// Gérer l'état vide vs rempli
const isEmpty = favoriteCount === 0;
// Titre émotionnel basé sur le nombre de favoris et la variante UX
if (title) {
if (UX_VARIANT === 'URGENCY') {
// Variante urgence
if (isEmpty) {
title.innerHTML = `⏳ Commencez par sélectionner vos favoris ❤️`;
} else if (favoriteCount < 5) {
title.innerHTML = `⏳ ${favoriteCount} souvenirs - Commandez vite !`;
} else if (favoriteCount < 12) {
title.innerHTML = `⏳ ${favoriteCount} magnifiques souvenirs - Plus que X jours !`;
} else {
title.innerHTML = `⏳ Superbe collection de ${favoriteCount} photos - Dernière chance !`;
}
} else {
// Variante émotion (par défaut)
if (isEmpty) {
title.innerHTML = `Commencez par sélectionner vos favoris ❤️`;
} else if (favoriteCount < 5) {
title.innerHTML = `✨ ${favoriteCount} souvenirs sélectionnés !`;
} else if (favoriteCount < 12) {
title.innerHTML = `🎉 Vous avez ${favoriteCount} souvenirs favoris`;
} else {
title.innerHTML = `🎊 Superbe collection de ${favoriteCount} photos !`;
}
}
}
// Sous-titre dynamique
if (subtitle) {
if (isEmpty) {
subtitle.innerHTML = `Cliquez sur ❤️ pour ajouter des photos à vos favoris`;
} else if (favoriteCount === 1) {
subtitle.innerHTML = `1 photo sélectionnée avec amour ❤️`;
} else {
subtitle.innerHTML = `${favoriteCount} photos sélectionnées avec amour ❤️`;
}
}
// Mettre à jour les compteurs produits
console.log('🔄 Updating sidebar counters:', { favoriteCount, albumCount, digitalCount, printsCount });
if (albumCount) {
albumCount.textContent = favoriteCount;
console.log('✅ Album count updated to:', favoriteCount);
}
if (digitalCount) {
digitalCount.textContent = favoriteCount;
console.log('✅ Digital count updated to:', favoriteCount);
}
// Afficher la pochette tirages seulement si >= 12 favoris
if (printsCard) {
if (favoriteCount >= 12) {
printsCard.style.display = 'block';
if (printsCount) printsCount.textContent = Math.min(favoriteCount, 12);
} else {
printsCard.style.display = 'none';
}
}
// Gérer l'état des CTA produits
updateProductCTAs(favoriteCount);
// Gérer le countdown si proche de l'expiration
updateCountdownTimer();
}
// Fonction pour gérer l'état des CTA produits
window.updateProductCTAs = function updateProductCTAs(favoriteCount) {
const isEmpty = favoriteCount === 0;
const productCTAs = document.querySelectorAll('.product-cta');
const productDescriptions = document.querySelectorAll('.product-description');
productCTAs.forEach((cta, index) => {
if (isEmpty) {
// État désactivé
cta.disabled = true;
cta.setAttribute('aria-disabled', 'true');
cta.style.cursor = 'not-allowed';
cta.style.opacity = '0.6';
cta.title = 'Ajoutez des favoris pour commander';
} else {
// État activé
cta.disabled = false;
cta.removeAttribute('aria-disabled');
cta.style.cursor = 'pointer';
cta.style.opacity = '1';
cta.title = '';
}
});
// Mettre à jour les descriptions des produits
productDescriptions.forEach((desc, index) => {
if (isEmpty) {
// Texte générique sans compteur
if (index === 0) { // Album
desc.innerHTML = 'Personnalisez votre album avec vos photos favorites. Papier de qualité supérieure, reliure rigide.';
} else if (index === 1) { // Pack numérique
desc.innerHTML = 'Téléchargement immédiat de vos photos en haute définition. Qualité professionnelle garantie.';
} else if (index === 2) { // Tirages
desc.innerHTML = 'Tirages photo 10x15cm de vos favoris. Papier brillant professionnel, livraison gratuite.';
}
} else {
// Texte avec compteur dynamique
if (index === 0) { // Album
desc.innerHTML = `Album photo personnalisé avec vos <strong>${favoriteCount}</strong> photos favorites. Papier de qualité supérieure, reliure rigide.`;
} else if (index === 1) { // Pack numérique
desc.innerHTML = `Téléchargement immédiat de vos <strong>${favoriteCount}</strong> photos en haute définition. Qualité professionnelle garantie.`;
} else if (index === 2) { // Tirages
const printsCount = Math.min(favoriteCount, 12);
desc.innerHTML = `<strong>${printsCount}</strong> tirages photo 10x15cm de vos favoris. Papier brillant professionnel, envoi sous 48h.`;
}
}
});
// Mettre à jour les icônes de favoris dans les produits
const albumFavCount = document.getElementById('album-fav-count');
const digitalFavCount = document.getElementById('digital-fav-count');
const printsFavCount = document.getElementById('prints-fav-count');
if (albumFavCount) albumFavCount.textContent = favoriteCount;
if (digitalFavCount) digitalFavCount.textContent = favoriteCount;
if (printsFavCount) printsFavCount.textContent = Math.min(favoriteCount, 12);
}
// Fonction pour gérer le countdown
window.updateCountdownTimer = function updateCountdownTimer() {
const countdownTimer = document.getElementById('countdown-timer');
const daysLeftSpan = document.getElementById('days-left');
if (!countdownTimer || !daysLeftSpan) return;
// Si pas de date définie, utiliser une date par défaut (6 semaines à partir d'aujourd'hui)
let expirationDate;
if (sejourEndDate) {
const endDate = new Date(sejourEndDate);
expirationDate = new Date(endDate.getTime() + (6 * 7 * 24 * 60 * 60 * 1000)); // +6 semaines
} else {
// Date par défaut : 6 semaines à partir d'aujourd'hui
expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + (6 * 7));
}
const now = new Date();
const daysLeft = Math.ceil((expirationDate - now) / (24 * 60 * 60 * 1000));
if (daysLeft <= 10 && daysLeft > 0) {
countdownTimer.style.display = 'block';
daysLeftSpan.textContent = daysLeft;
// Mettre à jour les titres avec le countdown si variante URGENCY
if (UX_VARIANT === 'URGENCY') {
const title = document.getElementById('ecommerce-title');
if (title) {
const favoriteCount = parseInt(document.getElementById('giftCount')?.textContent || '0');
if (favoriteCount > 0) {
if (favoriteCount < 5) {
title.innerHTML = `⏳ ${favoriteCount} souvenirs - Plus que ${daysLeft} jours !`;
} else if (favoriteCount < 12) {
title.innerHTML = `⏳ ${favoriteCount} magnifiques souvenirs - Plus que ${daysLeft} jours !`;
} else {
title.innerHTML = `⏳ Superbe collection de ${favoriteCount} photos - Plus que ${daysLeft} jours !`;
}
}
}
}
} else {
countdownTimer.style.display = 'none';
}
}
// Fonction pour commander un produit
window.orderProduct = function orderProduct(productType) {
let favoriteCount = 0;
// Méthode 1: Fonction getCurrentFavoriteCount
try {
if (typeof getCurrentFavoriteCount === 'function') {
favoriteCount = getCurrentFavoriteCount();
}
} catch (e) {
console.log('⚠️ getCurrentFavoriteCount non disponible:', e);
}
// Méthode 2: Element giftCount
if (favoriteCount === 0) {
const giftCount = document.getElementById('giftCount');
if (giftCount && giftCount.textContent) {
favoriteCount = parseInt(giftCount.textContent.trim()) || 0;
}
}
// Méthode 3: Récupérer depuis les éléments qui affichent le nombre de favoris
if (favoriteCount === 0) {
const albumFavCount = document.getElementById('album-fav-count');
const digitalFavCount = document.getElementById('digital-fav-count');
if (albumFavCount && albumFavCount.textContent) {
favoriteCount = parseInt(albumFavCount.textContent.trim()) || 0;
} else if (digitalFavCount && digitalFavCount.textContent) {
favoriteCount = parseInt(digitalFavCount.textContent.trim()) || 0;
}
}
// Méthode 4: Variable globale likes
if (favoriteCount === 0 && typeof likes !== 'undefined' && likes) {
favoriteCount = likes.length || 0;
}
// Méthode 5: Compter les éléments liked dans le DOM
if (favoriteCount === 0) {
const likedElements = document.querySelectorAll('.liked, .photo.liked, [data-liked="true"]');
favoriteCount = likedElements.length;
}
// Debug pour voir la valeur récupérée
console.log('🔍 Nombre de favoris détecté:', favoriteCount);
if (favoriteCount === 0) {
// Afficher une notification plus élégante
const notification = document.createElement('div');
notification.className = 'favorite-notification';
notification.innerHTML = `
<div class="notification-content">
<i class="bi bi-heart" style="color: #e91e63; font-size: 1.5rem; margin-right: 10px;"></i>
<span>Veuillez d'abord sélectionner des photos favorites !</span>
<button onclick="this.parentElement.parentElement.remove()" style="background: none; border: none; color: #666; font-size: 1.2rem; margin-left: 10px;">×</button>
</div>
`;
// Styles pour la notification
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #fff;
border: 2px solid #e91e63;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
animation: slideInRight 0.3s ease;
`;
// Ajouter l'animation CSS si elle n'existe pas
if (!document.getElementById('notification-styles')) {
const style = document.createElement('style');
style.id = 'notification-styles';
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.notification-content {
display: flex;
align-items: center;
}
`;
document.head.appendChild(style);
}
document.body.appendChild(notification);
// Supprimer automatiquement après 5 secondes
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 5000);
return;
}
// Construire l'URL avec les favoris pré-sélectionnés
let orderUrl = '';
switch (productType) {
case 'album':
orderUrl = `{{ path('EditionAlbum') }}?favorites=${favoriteCount}`;
break;
case 'digital':
orderUrl = `{{ path('PackPhotosNumerique_Favoris', {'nbr': 15}) }}?favorites=${favoriteCount}`;
break;
case 'prints':
orderUrl = `{{ path('AjoutPochettePhotos_Favoris', {'nbr': 12}) }}?favorites=${favoriteCount}`;
break;
default:
console.error('Type de produit non reconnu:', productType);
alert('Erreur: Type de produit non reconnu');
return;
}
// Analytics/tracking
console.log('Product ordered', { productType, favoriteCount, url: orderUrl });
// Redirection vers la commande
if (orderUrl) {
// Vérifier que l'URL est valide
try {
new URL(orderUrl, window.location.origin);
window.location.href = orderUrl;
} catch (error) {
console.error('URL invalide générée:', orderUrl, error);
alert('Erreur: Impossible de générer le lien de commande');
}
} else {
console.error('Aucune URL générée pour le produit:', productType);
alert('Erreur: Impossible de générer le lien de commande');
}
}
// Fonction pour tester tous les liens de produits
window.testProductLinks = function() {
const products = ['album', 'digital', 'prints'];
const favoriteCount = window.getFavoriteCount() || 0;
console.log('🧪 Test des liens de produits avec', favoriteCount, 'favoris');
products.forEach(productType => {
try {
let testUrl = '';
switch (productType) {
case 'album':
testUrl = `{{ path('EditionAlbum') }}?favorites=${favoriteCount}`;
break;
case 'digital':
testUrl = `{{ path('PackPhotosNumerique_Favoris', {'nbr': 15}) }}?favorites=${favoriteCount}`;
break;
case 'prints':
testUrl = `{{ path('AjoutPochettePhotos_Favoris', {'nbr': 12}) }}?favorites=${favoriteCount}`;
break;
}
// Vérifier que l'URL est valide
const url = new URL(testUrl, window.location.origin);
console.log(`✅ ${productType}: ${url.href}`);
} catch (error) {
console.error(`❌ ${productType}: Erreur URL`, error);
}
});
};
// Fonction pour valider les liens au chargement de la page
window.validateProductLinks = function() {
// Vérifier que tous les boutons de produits existent
const albumBtn = document.querySelector('button[onclick*="orderProduct(\'album\')"]');
const digitalBtn = document.querySelector('button[onclick*="orderProduct(\'digital\')"]');
const printsBtn = document.querySelector('button[onclick*="orderProduct(\'prints\')"]');
if (!albumBtn) console.warn('⚠️ Bouton album non trouvé');
if (!digitalBtn) console.warn('⚠️ Bouton digital non trouvé');
if (!printsBtn) console.warn('⚠️ Bouton prints non trouvé');
// Tester les liens
window.testProductLinks();
};
// Améliorer la fonction updateAllFavoriteCounters existante
const originalUpdateAllFavoriteCounters = window.updateAllFavoriteCounters || function() {};
window.updateAllFavoriteCounters = function() {
// Appeler la fonction originale
originalUpdateAllFavoriteCounters();
// Animation rebond du compteur favoris
const giftCount = document.getElementById('giftCount');
if (giftCount) {
giftCount.classList.remove('gift-count-bounce');
setTimeout(() => giftCount.classList.add('gift-count-bounce'), 10);
}
// Animation rebond du compteur dans la bulle cadeau
const likeCount = document.getElementById('likeCount');
if (likeCount) {
likeCount.classList.remove('gift-count-bounce');
setTimeout(() => likeCount.classList.add('gift-count-bounce'), 10);
}
// Récupérer le nombre de favoris actuel avec la fonction utilitaire robuste
let favoriteCount = window.getFavoriteCount();
// Mettre à jour les compteurs dans le sidebar même s'il n'est pas ouvert
const albumCount = document.getElementById('album-count');
const digitalCount = document.getElementById('digital-count');
const printsCount = document.getElementById('prints-count');
if (albumCount) albumCount.textContent = favoriteCount;
if (digitalCount) digitalCount.textContent = favoriteCount;
if (printsCount) printsCount.textContent = Math.min(favoriteCount, 12);
// Mettre à jour les icônes de favoris dans les produits
const albumFavCount = document.getElementById('album-fav-count');
const digitalFavCount = document.getElementById('digital-fav-count');
const printsFavCount = document.getElementById('prints-fav-count');
if (albumFavCount) albumFavCount.textContent = favoriteCount;
if (digitalFavCount) digitalFavCount.textContent = favoriteCount;
if (printsFavCount) printsFavCount.textContent = Math.min(favoriteCount, 12);
// Mettre à jour le sidebar si ouvert
const sidebar = document.getElementById('ecommerce-sidebar');
if (sidebar && sidebar.classList.contains('active')) {
console.log('🔄 Sidebar is open, updating content with favoriteCount:', favoriteCount);
window.updateEcommerceSidebarContent(favoriteCount);
} else {
console.log('ℹ️ Sidebar is closed, but updating content anyway for consistency');
// Mettre à jour le contenu même si le sidebar n'est pas ouvert pour la cohérence
window.updateEcommerceSidebarContent(favoriteCount);
}
};
// Fonction pour retirer un favori de la grille et exécuter supprimerFavoris
window.removeFavorite = function removeFavorite(itemId) {
// Animation simple de disparition puis suppression
const favoriteItem = document.querySelector(`.favorite-item[data-id="${itemId}"]`);
if (favoriteItem) {
favoriteItem.classList.add('fade-out');
setTimeout(() => {
favoriteItem.remove();
// Appeler la fonction métier si présente
if (typeof window.supprimerFavoris === 'function') {
const heartIcon = document.querySelector(`#coeur${itemId}`);
const sejourId = heartIcon && heartIcon.dataset.sejourId ? heartIcon.dataset.sejourId : '';
window.supprimerFavoris(itemId, sejourId);
}
// Mettre à jour les compteurs
updateAllFavoriteCounters();
}, 200);
}
};
// Vérifier que la fonction est bien définie
console.log('🔍 removeFavorite function defined:', typeof window.removeFavorite);
// (Nettoyé) Pas de délégation globale ici; les boutons utilisent des onclick simples
// Fonction pour voir un favori en grand
window.viewFavorite = function viewFavorite(itemId) {
console.log('👁️ Viewing favorite:', itemId);
// Essayer d'utiliser la fonction viewImage existante
if (typeof window.viewImage === 'function') {
window.viewImage(itemId, null);
} else {
console.log('ℹ️ viewImage function not available, opening in new tab');
// Fallback: ouvrir dans un nouvel onglet
const favoriteItem = document.querySelector(`.favorite-item[data-id="${itemId}"] img`);
if (favoriteItem && favoriteItem.src) {
window.open(favoriteItem.src, '_blank');
}
}
};
// Fonction de debug pour tester les compteurs
window.debugFavoriteCounters = function() {
console.log('🔍 Debug Favorite Counters:');
console.log('- getFavoriteCount():', window.getFavoriteCount());
console.log('- likeCount input value:', document.getElementById('likeCount')?.value);
console.log('- likeCount textContent:', document.getElementById('likeCount')?.textContent);
console.log('- giftCount textContent:', document.getElementById('giftCount')?.textContent);
console.log('- album-count textContent:', document.getElementById('album-count')?.textContent);
console.log('- digital-count textContent:', document.getElementById('digital-count')?.textContent);
console.log('- prints-count textContent:', document.getElementById('prints-count')?.textContent);
console.log('- Sidebar active:', document.getElementById('ecommerce-sidebar')?.classList.contains('active'));
};
// Fonction de test pour les boutons de favoris
window.testFavoriteButtons = function() {
console.log('🧪 Testing favorite buttons:');
const removeButtons = document.querySelectorAll('.btn-remove-favorite');
const viewButtons = document.querySelectorAll('.btn-view-favorite');
console.log('- Remove buttons found:', removeButtons.length);
console.log('- View buttons found:', viewButtons.length);
removeButtons.forEach((btn, index) => {
console.log(`- Remove button ${index}:`, {
itemId: btn.dataset.itemId,
hasClickListener: btn.onclick !== null
});
});
viewButtons.forEach((btn, index) => {
console.log(`- View button ${index}:`, {
itemId: btn.dataset.itemId,
hasClickListener: btn.onclick !== null
});
});
};
// Fermer le sidebar en cliquant en dehors
document.addEventListener('click', function(e) {
const sidebar = document.getElementById('ecommerce-sidebar');
const giftButton = document.querySelector('.gift-button');
if (sidebar && sidebar.classList.contains('active') &&
!sidebar.contains(e.target) &&
!giftButton.contains(e.target)) {
window.closeEcommerceSidebar();
}
});
// Fermer avec Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
window.closeEcommerceSidebar();
}
});
// Initialisation au chargement
document.addEventListener('DOMContentLoaded', function() {
// Vérifier que les éléments existent
const sidebar = document.getElementById('ecommerce-sidebar');
const giftButton = document.getElementById('gift-button-trigger');
// Event listener propre pour le bouton cadeau
if (giftButton) {
giftButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
window.openEcommerceSidebar();
});
}
// Définir la date de fin du séjour si disponible
{% if sejour and sejour.dateFinSejour is defined %}
window.setSejourEndDate('{{ sejour.dateFinSejour|date("Y-m-d") }}');
{% endif %}
// Mise à jour initiale du contenu
let favoriteCount = 0;
try {
if (typeof getCurrentFavoriteCount === 'function') {
favoriteCount = getCurrentFavoriteCount();
} else {
// Essayer d'abord l'input likeCount
const likeCountInput = document.getElementById('likeCount');
if (likeCountInput && likeCountInput.value) {
favoriteCount = parseInt(likeCountInput.value, 10) || 0;
} else {
// Fallback sur giftCount
const giftCount = document.getElementById('giftCount');
if (giftCount && giftCount.textContent) {
favoriteCount = parseInt(giftCount.textContent.trim()) || 0;
}
}
}
} catch (e) {
console.log('⚠️ Fallback pour favoriteCount initial:', e);
// Fallback sur likeCount input
const likeCountInput = document.getElementById('likeCount');
if (likeCountInput && likeCountInput.value) {
favoriteCount = parseInt(likeCountInput.value, 10) || 0;
} else {
const giftCount = document.getElementById('giftCount');
if (giftCount && giftCount.textContent) {
favoriteCount = parseInt(giftCount.textContent.trim()) || 0;
}
}
}
window.updateEcommerceSidebarContent(favoriteCount);
});
</script>
<!-- 🎯 DAY FILTER POPOVER (instancié une seule fois) -->
<div id="dayFilterPopover" class="day-popover" role="dialog" aria-modal="true" aria-hidden="true">
<div class="dp-arrow" aria-hidden="true"></div>
<div class="dp-content" role="group" aria-label="Filtres du jour">
<button class="dp-btn" data-filter="all" title="Tout" aria-pressed="true">
<i class="bi bi-grid-3x3-gap-fill"></i><span class="dp-count dp-label">Tout</span>
</button>
<button class="dp-btn" data-filter="photos" title="Photos" aria-pressed="false">
<i class="bi bi-images"></i><span class="dp-count" data-bind="photo">0</span>
</button>
<button class="dp-btn" data-filter="audio" title="Audios" aria-pressed="false">
<i class="bi bi-mic-fill"></i><span class="dp-count" data-bind="audio">0</span>
</button>
<button class="dp-btn" data-filter="videos" title="Vidéos" aria-pressed="false">
<i class="bi bi-camera-video-fill"></i><span class="dp-count" data-bind="video">0</span>
</button>
<button class="dp-btn" data-filter="favoris" title="Favoris" aria-pressed="false">
<i class="bi bi-heart-fill" style="color:#f56040"></i><span class="dp-count" data-bind="fav">0</span>
</button>
<button class="dp-btn dp-close" title="Fermer" aria-label="Fermer">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
{% endblock %}