<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
/* Parent/DetailsSejour.html.twig */
class __TwigTemplate_2e4d9af98367fb875e9538af7eac3dac extends Template
{
private Source $source;
/**
* @var array<string, Template>
*/
private array $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->blocks = [
'LinksCss' => [$this, 'block_LinksCss'],
'Content' => [$this, 'block_Content'],
'Javascript' => [$this, 'block_Javascript'],
];
}
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
{
// line 1
return "Parent/LayoutParent.html.twig";
}
protected function doDisplay(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "Parent/DetailsSejour.html.twig"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "Parent/DetailsSejour.html.twig"));
// line 33
$context["pageMenu"] = CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 33, $this->source); })()), "session", [], "any", false, false, false, 33), "get", ["pageMenu"], "method", false, false, false, 33);
// line 1
$this->parent = $this->load("Parent/LayoutParent.html.twig", 1);
yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks));
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
}
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_LinksCss(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "LinksCss"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "LinksCss"));
// line 2
yield from $this->yieldParentBlock("LinksCss", $context, $blocks);
yield "
<script src=\"https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.js\" defer></script>
<link rel=\"stylesheet\" href=\"";
// line 5
yield "/css/Parent/css/premiercnx.css";
yield "\" />
<link href=\"";
// line 6
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("css/Parent/css/detailsejour.css"), "html", null, true);
yield "\" type=\"text/css\" rel=\"stylesheet\" />
<link rel=\"stylesheet\" href=\"";
// line 7
yield "/css/Accompagnateur/imgzoom.css";
yield "\" />
<link rel=\"stylesheet\" href=\"";
// line 8
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("Plugins/css/dropzone.css"), "html", null, true);
yield "\" />
<link rel=\"stylesheet\" href=\"";
// line 9
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("css/splide.min.css"), "html", null, true);
yield "\" />
<link rel=\"stylesheet\" href=\"";
// line 10
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("css/favorites-sidebar.css"), "html", null, true);
yield "\" />
";
// line 11
$context["destination"] = "detailsejour";
// line 12
yield "<style>
.btn-close {
font-size: 1.2rem;
border: none;
background: transparent;
}
.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>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 33
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_Content(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "Content"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "Content"));
// line 35
yield "
";
// line 37
yield from $this->load("components/parents/_cro_layers.html.twig", 37)->unwrap()->yield(CoreExtension::merge($context, ["sejour" => (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 37, $this->source); })()), "ctx" => ((array_key_exists("ctx", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["ctx"]) || array_key_exists("ctx", $context) ? $context["ctx"] : (function () { throw new RuntimeError('Variable "ctx" does not exist.', 37, $this->source); })()), [])) : ([]))]));
// line 38
yield "
<!-- 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\"
>
";
// line 72
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 72, $this->source); })())), "html", null, true);
yield "
</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\">
";
// line 97
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 97, $this->source); })())), "html", null, true);
yield " 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\">";
// line 135
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 135, $this->source); })())), "html", null, true);
yield "</span>
</div>
</div>
<p class=\"product-description\">
Album photo personnalisé avec vos <strong id=\"album-count\">";
// line 139
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 139, $this->source); })())), "html", null, true);
yield "</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\">";
// line 160
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 160, $this->source); })())), "html", null, true);
yield "</span>
</div>
</div>
<p class=\"product-description\">
Téléchargement immédiat de vos <strong id=\"digital-count\">";
// line 164
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 164, $this->source); })())), "html", null, true);
yield "</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 : ";
// line 206
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 206, $this->source); })())), "html", null, true);
yield " / 12</p>
<p>Numériques : ";
// line 207
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 207, $this->source); })())), "html", null, true);
yield " / 15</p>
<p>Album : ";
// line 208
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), (isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 208, $this->source); })())), "html", null, true);
yield " / 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=\"";
// line 360
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 360, $this->source); })()), "html", null, true);
yield "\" />
<input id=\"likeCount\" type=\"hidden\" value=\"";
// line 361
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 361, $this->source); })()), "html", null, true);
yield "\" />
<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=\"";
// line 389
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("/images/imgSliderEmpty2.png"), "html", null, true);
yield "\"
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=\"";
// line 413
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("/images/imgSliderEmpty1.png"), "html", null, true);
yield "\"
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\" style=\"display:flex; align-items:center; gap:10px;\">Séjour ";
// line 436
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 436, $this->source); })()), "themSejour", [], "any", false, false, false, 436), "html", null, true);
yield "
<button id=\"croTestAll\" class=\"btn btn-sm btn-outline-secondary d-none\">
Tester CRO
</button>
</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 ";
// line 444
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 444, $this->source); })()), "html", null, true);
yield " photos et vidéos partagées\">
<i class=\"bi bi-images\"></i> ";
// line 445
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 445, $this->source); })()), "html", null, true);
yield "
</button>
<button class=\"pill stat-pill\" data-filter=\"audios\" aria-label=\"Voir ";
// line 447
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nbmessages"]) || array_key_exists("nbmessages", $context) ? $context["nbmessages"] : (function () { throw new RuntimeError('Variable "nbmessages" does not exist.', 447, $this->source); })()), "html", null, true);
yield " messages audio enregistrés\">
<i class=\"bi bi-mic-fill\"></i> ";
// line 448
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nbmessages"]) || array_key_exists("nbmessages", $context) ? $context["nbmessages"] : (function () { throw new RuntimeError('Variable "nbmessages" does not exist.', 448, $this->source); })()), "html", null, true);
yield "
</button>
<button class=\"pill stat-pill\" data-filter=\"favoris\" aria-label=\"Voir ";
// line 450
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 450, $this->source); })()), "html", null, true);
yield " contenus marqués en favoris\">
<i class=\"bi bi-heart-fill\"></i> ";
// line 451
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 451, $this->source); })()), "html", null, true);
yield "
</button>
</nav>
</div>
</div>
<!-- Ligne 2: Dates (gris clair) -->
<div class=\"header-line-2\">
du ";
// line 459
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 459, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 459), "d M Y"), ["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."]), "html", null, true);
yield "
au ";
// line 460
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 460, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 460), "d M Y"), ["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."]), "html", null, true);
yield "
· Code : ";
// line 461
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 461, $this->source); })()), "codeSejour", [], "any", false, false, false, 461), "html", null, true);
yield "
</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 ";
// line 471
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 471, $this->source); })()), "html", null, true);
yield "</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\">";
// line 494
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 494, $this->source); })()), "html", null, true);
yield "</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\">
";
// line 512
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["listeattach"]) || array_key_exists("listeattach", $context) ? $context["listeattach"] : (function () { throw new RuntimeError('Variable "listeattach" does not exist.', 512, $this->source); })()));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["x"] => $context["groupAttach"]) {
// line 513
yield " ";
$context["xDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate($context["x"]);
// line 514
yield " ";
$context["finDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 514, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 514));
// line 515
yield " ";
if (((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 515, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 515, $this->source); })()))) {
// line 516
yield " <div
class=\"date-card modern-card ";
// line 517
if ((CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 517) && ((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 517, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 517, $this->source); })())))) {
yield " active ";
}
yield "\"
data-aos=\"fade-up\"
data-bs-toggle=\"collapse\"
data-day-id=\"";
// line 520
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 520, $this->source); })()), "Y-m-d"), "html", null, true);
yield "\"
data-bs-target=\"#demP";
// line 521
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 521), "html", null, true);
yield "\"
id=\"iconedemoP";
// line 522
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 522), "html", null, true);
yield "\"
role=\"button\"
aria-label=\"Contenu du ";
// line 524
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 524, $this->source); })()), "d F Y"), ["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"]), "html", null, true);
// line 528
yield " : ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 528), "html", null, true);
yield " photos, ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 528), "html", null, true);
yield " audios, ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 528), "html", null, true);
yield " vidéos\"
tabindex=\"0\"
>
<div class=\"card-content\">
<span class=\"title-line\">
<span class=\"full-date\">
";
// line 535
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 535, $this->source); })()), "D"), ["Mon" => "lun.", "Tue" => "mar.", "Wed" => "mer.", "Thu" => "jeu.", "Fri" => "ven.", "Sat" => "sam.", "Sun" => "dim."]), "html", null, true);
// line 537
yield "
";
// line 539
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 539, $this->source); })()), "d M"), ["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."]), "html", null, true);
// line 542
yield "
";
// line 543
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "isFirstDay", [], "any", false, false, false, 543) == "yes")) {
// line 544
yield " <span class=\"day-badge first\">Début</span>
";
} elseif ((CoreExtension::getAttribute($this->env, $this->source, // line 545
$context["groupAttach"], "isLastDay", [], "any", false, false, false, 545) == "yes")) {
// line 546
yield " <span class=\"day-badge last\">Dernier</span>
";
}
// line 550
yield " </span>
<span class=\"badge-new d-none\" aria-label=\"Nouveau contenu\"></span>
</span>
<ul class=\"media-list-horizontal\">
";
// line 558
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 558) > 0)) {
// line 559
yield " <li><i class=\"bi bi-images\"></i>";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 559), "html", null, true);
yield "</li>
";
}
// line 561
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 561) > 0)) {
// line 562
yield " <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>";
// line 565
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 565), "html", null, true);
yield "</li>
";
}
// line 567
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 567) > 0)) {
// line 568
yield " <li><i class=\"bi bi-camera-video-fill\"></i>";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 568), "html", null, true);
yield "</li>
";
}
// line 570
yield " </ul>
</div>
</div>
";
}
// line 574
yield " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['x'], $context['groupAttach'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 575
yield " </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\">
";
// line 590
$context["lastValidIndex"] = 0;
// line 591
yield " ";
$context["hasAttachments"] = false;
// line 592
yield " ";
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["listeattach"]) || array_key_exists("listeattach", $context) ? $context["listeattach"] : (function () { throw new RuntimeError('Variable "listeattach" does not exist.', 592, $this->source); })()));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["x"] => $context["groupAttach"]) {
// line 593
yield " ";
$context["xDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate($context["x"]);
// line 594
yield " ";
$context["finDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 594, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 594));
// line 595
yield " ";
if (((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 595, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 595, $this->source); })()))) {
// line 596
yield " ";
$context["lastValidIndex"] = CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 596);
// line 597
yield " ";
$context["hasAttachments"] = true;
// line 598
yield " <div
id=\"demP";
// line 599
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 599), "html", null, true);
yield "\"
class=\"collapse ";
// line 600
if ((CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 600) && ((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 600, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 600, $this->source); })())))) {
yield "show";
}
yield "\"
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;\">
";
// line 630
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::replace($this->extensions['Twig\Extension\CoreExtension']->formatDate($context["x"], "l d F Y"), ["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"]), "html", null, true);
// line 638
yield "
</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>
";
// line 647
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 647) > 0)) {
// line 648
yield " <span class=\"filter-badge\" data-filter=\"photo\" title=\"Filtrer les photos\">
<i class=\"bi bi-image\" style=\"margin-right: 5px; font-size: 14px;\"></i> ";
// line 649
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 649), "html", null, true);
yield "
</span>
";
}
// line 652
yield "
";
// line 653
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 653) > 0)) {
// line 654
yield " <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> ";
// line 655
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 655), "html", null, true);
yield "
</span>
";
}
// line 658
yield "
";
// line 659
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 659) > 0)) {
// line 660
yield " <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> ";
// line 661
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 661), "html", null, true);
yield "
</span>
";
}
// line 664
yield "</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=\"";
// line 682
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 682), "html", null, true);
yield "\">
<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>";
// line 695
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", true, true, false, 695)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 695), 0)) : (0)), "html", null, true);
yield "</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>";
// line 700
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", true, true, false, 700)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 700), 0)) : (0)), "html", null, true);
yield "</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>";
// line 705
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", true, true, false, 705)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 705), 0)) : (0)), "html", null, true);
yield "</span>
</div>
</div>
</div>
<p class=\"description\" style=\"margin-left:2%;width:95%;margin-top:1%;margin-bottom:1%;text-align:left\">
";
// line 711
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 711, $this->source); })()), "jourdescripdate", [], "any", false, false, false, 711));
foreach ($context['_seq'] as $context["_key"] => $context["description"]) {
yield " ";
if (($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, // line 712
$context["description"], "datejourphoto", [], "any", false, false, false, 712), "m/d/Y") == $this->extensions['Twig\Extension\CoreExtension']->formatDate($context["x"], "m/d/Y"))) {
// line 713
yield " ";
yield Twig\Extension\CoreExtension::nl2br($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["description"], "description", [], "any", false, false, false, 713), "html", null, true));
yield "
";
}
// line 714
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['description'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 715
yield " </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 -->
";
// line 730
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 730));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 731
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 731) == "photo")) {
// line 732
yield "
<div class=\"column\" data-type=\"";
// line 733
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 733), "html", null, true);
yield "\">
<div class=\"photo-zoom photo-item\">
<!-- Image sans lien -->
<img src=\"";
// line 737
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 737), "html", null, true);
yield "\" alt=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 737), "html", null, true);
yield "\" loading=\"lazy\" decoding=\"async\" />
<!-- Icône \"voir\" pour ouvrir le slider -->
<div class=\"view-icon\" onclick=\"viewImage('";
// line 740
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 740), "html", null, true);
yield "', 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";
// line 747
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 747), "html", null, true);
yield "\"
data-id=\"";
// line 748
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 748), "html", null, true);
yield "\"
data-sejour-id=\"";
// line 749
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 749, $this->source); })()), "id", [], "any", false, false, false, 749), "html", null, true);
yield "\"
data-path=\"";
// line 750
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 750), "html", null, true);
yield "\"
data-description=\"";
// line 751
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 751), "html", null, true);
yield "\"
>
";
// line 753
if ((($tmp = CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 753, $this->source); })()), "user", [], "any", false, false, false, 753)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
yield " ";
if ((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "is_favorite", [], "any", false, false, false, 753)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 754
yield " <i
class=\"bi bi-heart-fill\"
title=\"Sélectionnée\"
style=\"color: #f56040\"
></i>
";
} else {
// line 760
yield " <i class=\"bi bi-heart\" title=\"Ajouter à ma sélection\"></i>
";
}
// line 761
yield " ";
}
// line 762
yield " </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>
";
// line 776
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 776) != "")) {
// line 777
yield " <h4 class=\"description\">";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 777), "html", null, true);
yield "</h4>
";
}
// line 779
yield " </div>
";
}
// line 783
yield "
";
// line 786
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 786) == "video")) {
// line 787
yield "
<div class=\"column\" data-type=\"";
// line 788
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 788), "html", null, true);
yield "\">
<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=\"";
// line 793
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 793), "html", null, true);
yield "\" type=\"video/mp4\" />
Votre navigateur ne supporte pas la lecture vidéo.
</video>
</div>
";
// line 798
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 798) != "")) {
// line 799
yield " <h4 class=\"description\">";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 799), "html", null, true);
yield "</h4>
";
}
// line 801
yield " </div>
";
}
// line 802
yield "
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 805
yield " </div>
<!-- Section séparée pour les messages audio -->
";
// line 808
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 808) > 0)) {
// line 809
yield " <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 (";
// line 813
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 813), "html", null, true);
yield ")
</h4>
";
// line 816
if ((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 816, $this->source); })()), "codeSejour", [], "any", false, false, false, 816), 0, 2) == "PF")) {
// line 817
yield " ";
// line 818
yield " <div class=\"audio-messages-container\" data-type=\"audio\" style=\"display: flex; flex-wrap: wrap; gap: 15px\">
";
// line 819
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 819));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 820
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 820) == "message")) {
// line 821
yield " <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=\"";
// line 825
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 825), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 829
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 829) != "")) {
// line 830
yield " <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%;\">
";
// line 832
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 832), "html", null, true);
yield "
</p>
</div>
";
}
// line 836
yield " </div>
";
}
// line 838
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 839
yield " </div>
";
} elseif ((((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, // line 841
(isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 841, $this->source); })()), "codeSejour", [], "any", false, false, false, 841), 0, 2) == "PP") && (CoreExtension::getAttribute($this->env, $this->source, (isset($context["parentsejour"]) || array_key_exists("parentsejour", $context) ? $context["parentsejour"] : (function () { throw new RuntimeError('Variable "parentsejour" does not exist.', 841, $this->source); })()), "payment", [], "any", false, false, false, 841) == 1)) || ((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 841, $this->source); })()), "codeSejour", [], "any", false, false, false, 841), 0, 2) == "EF") && (CoreExtension::getAttribute($this->env, $this->source, (isset($context["parentsejour"]) || array_key_exists("parentsejour", $context) ? $context["parentsejour"] : (function () { throw new RuntimeError('Variable "parentsejour" does not exist.', 841, $this->source); })()), "payment", [], "any", false, false, false, 841) == 1)))) {
// line 842
yield " ";
// line 843
yield " <div class=\"audio-messages-container\" data-type=\"audio\" style=\"display: flex; flex-wrap: wrap; gap: 15px\">
";
// line 844
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 844));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 845
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 845) == "message")) {
// line 846
yield " <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=\"";
// line 850
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 850), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 854
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 854) != "")) {
// line 855
yield " <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%;\">
";
// line 857
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 857), "html", null, true);
yield "
</p>
</div>
";
}
// line 861
yield " </div>
";
}
// line 863
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 864
yield " </div>
";
} elseif ((((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, // line 866
(isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 866, $this->source); })()), "codeSejour", [], "any", false, false, false, 866), 0, 2) == "PP") && (CoreExtension::getAttribute($this->env, $this->source, (isset($context["parentsejour"]) || array_key_exists("parentsejour", $context) ? $context["parentsejour"] : (function () { throw new RuntimeError('Variable "parentsejour" does not exist.', 866, $this->source); })()), "payment", [], "any", false, false, false, 866) == 0)) || ((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 866, $this->source); })()), "codeSejour", [], "any", false, false, false, 866), 0, 2) == "EF") && (CoreExtension::getAttribute($this->env, $this->source, (isset($context["parentsejour"]) || array_key_exists("parentsejour", $context) ? $context["parentsejour"] : (function () { throw new RuntimeError('Variable "parentsejour" does not exist.', 866, $this->source); })()), "payment", [], "any", false, false, false, 866) == 0)))) {
// line 867
yield " ";
// line 868
yield " <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>
";
// line 873
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 873));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 874
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 874) == "message")) {
// line 875
yield " <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=\"";
// line 879
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 879), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 883
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 883) != "")) {
// line 884
yield " <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%;\">
";
// line 886
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 886), "html", null, true);
yield "
</p>
</div>
";
}
// line 890
yield " </div>
";
}
// line 892
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 893
yield " </div>
";
}
// line 895
yield " </div>
";
}
// line 897
yield " </div>
</div>
</div>
";
}
// line 901
yield " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['revindex0'], $context['loop']['revindex'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['x'], $context['groupAttach'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 902
yield " ";
if ((($tmp = !(isset($context["hasAttachments"]) || array_key_exists("hasAttachments", $context) ? $context["hasAttachments"] : (function () { throw new RuntimeError('Variable "hasAttachments" does not exist.', 902, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 903
yield " <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>
";
}
// line 938
yield "
<!-- 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' -->
";
// line 3527
if (((isset($context["lastValidIndex"]) || array_key_exists("lastValidIndex", $context) ? $context["lastValidIndex"] : (function () { throw new RuntimeError('Variable "lastValidIndex" does not exist.', 3527, $this->source); })()) > 0)) {
// line 3528
yield " <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";
// line 3533
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["lastValidIndex"]) || array_key_exists("lastValidIndex", $context) ? $context["lastValidIndex"] : (function () { throw new RuntimeError('Variable "lastValidIndex" does not exist.', 3533, $this->source); })()), "html", null, true);
yield "');
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 >= ";
// line 3541
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["lastValidIndex"]) || array_key_exists("lastValidIndex", $context) ? $context["lastValidIndex"] : (function () { throw new RuntimeError('Variable "lastValidIndex" does not exist.', 3541, $this->source); })()), "html", null, true);
yield ") {
dateCards.forEach(card => {
if (card && card.classList) {
card.classList.remove('active');
}
});
const targetCard = dateCards[";
// line 3547
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((isset($context["lastValidIndex"]) || array_key_exists("lastValidIndex", $context) ? $context["lastValidIndex"] : (function () { throw new RuntimeError('Variable "lastValidIndex" does not exist.', 3547, $this->source); })()) - 1), "html", null, true);
yield "];
if (targetCard && targetCard.classList) {
targetCard.classList.add('active');
}
}
}
}
});
</script>
";
}
// line 3557
yield " </div>
</div>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 3560
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_Javascript(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "Javascript"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "Javascript"));
// line 3561
yield " ";
yield from $this->yieldParentBlock("Javascript", $context, $blocks);
yield "
<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: \"";
// line 3808
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 20]);
yield "\",
},
+
{
name: \"Pochette photo (12 photos)\",
required: 12,
remaining: Math.max(0, 12 - current),
image: \"/images/produit/PochettePhoto5sur5-2.jpg\",
color: \"#2196f3\",
link: \"";
// line 3817
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("AjoutPochettePhotos_Favoris", ["nbr" => 12]);
yield "\",
},
].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='";
// line 3875
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "'\"
>
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=\"";
// line 4008
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("js/splide.min.js"), "html", null, true);
yield "\" 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=\"";
// line 4061
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("Accueil/js/jquery.magnific-popup.min.js"), "html", null, true);
yield "\"
></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 ";
// line 4083
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 4083, $this->source); })()), "html", null, true);
yield "
// 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: \"";
// line 4344
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("AjoutPochettePhotos_Favoris", ["nbr" => 12]);
yield "\",
},
{
name: \"Pack numérique (20 photos)\",
required: 20,
remaining: remainingForAlbum,
image: \"/images/produit/photoNumerique.jpg\",
color: \"#4caf50\",
link: \"";
// line 4352
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 20]);
yield "\",
}
].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='";
// line 4419
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "'\"
>
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 = ";
// line 5064
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 5064, $this->source); })()), "html", null, true);
yield ";
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: \"";
// line 5079
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("EditionAlbum");
yield "\"
});
}
if (favoriteCount >= 12) {
produits.push({
titre: \"Pochette débloquée !\",
bouton: \"Commander\",
image: \"/images/produit/PochettePhoto5sur5-2.jpg\",
lien: \"";
// line 5087
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("AjoutPochettePhotos_Favoris", ["nbr" => 12]);
yield "\"
});
}
if (favoriteCount >= 5) {
produits.push({
titre: \"Pack numérique débloqué !\",
bouton: \"Commander\",
image: \"/images/produit/photoNumerique.jpg\",
lien: \"";
// line 5095
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 15]);
yield "\"
});
}
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: \"";
// line 5220
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("Supprimer_fav");
yield "\",
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: \"";
// line 5294
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("Ajouter_fav");
yield "\",
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 () {
// Clé localStorage pour tracker la première visite
const sejourId = '";
// line 5569
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, ($context["sejour"] ?? null), "id", [], "any", true, true, false, 5569)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 5569, $this->source); })()), "id", [], "any", false, false, false, 5569), "")) : ("")), "html", null, true);
yield "';
const parentId = '";
// line 5570
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, ($context["app"] ?? null), "user", [], "any", false, true, false, 5570), "id", [], "any", true, true, false, 5570)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 5570, $this->source); })()), "user", [], "any", false, false, false, 5570), "id", [], "any", false, false, false, 5570), "")) : ("")), "html", null, true);
yield "';
const sliderKey = `imageSlider_seen_\${parentId}_\${sejourId}`;
const isFirstVisit = !localStorage.getItem(sliderKey);
const sliderContainer = document.querySelector('.divSliderModern');
const imageSlider = document.getElementById('imageSlider');
if (!isFirstVisit) {
// Pas la première visite : cacher le slider
if (sliderContainer) sliderContainer.style.display = 'none';
return; // Ne pas initialiser Splide ni l'autoscroll
}
// Première visite : afficher le slider et initialiser
if (sliderContainer) sliderContainer.style.display = 'block';
// Initialisation du carrousel Splide
var splide = new Splide(\"#imageSlider\", {
type: \"loop\",
perPage: 1,
autoplay: true,
interval: 6000,
pauseOnHover: false,
pauseOnFocus: false,
pagination: false,
arrows: false,
});
splide.mount();
// Fonction pour faire défiler automatiquement vers la section suivante
function scrollToNextSection() {
const targetSection = document.getElementById(\"scrollTarget\");
if (targetSection) {
const targetPosition =
targetSection.getBoundingClientRect().top + window.scrollY;
const adjustedPosition = targetPosition - 50;
window.scrollTo({
top: adjustedPosition,
behavior: \"smooth\",
});
}
}
// Démarrer le timer pour le scroll automatique après 5 secondes (première visite uniquement)
setTimeout(scrollToNextSection, 5000);
// Marquer comme vu après le premier scroll
setTimeout(() => {
localStorage.setItem(sliderKey, '1');
}, 6000);
});
</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 () {
";
// line 5701
if ((($tmp = CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 5701, $this->source); })()), "session", [], "any", false, false, false, 5701), "get", ["paymentmoniteco"], "method", false, false, false, 5701)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 5702
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 5702, $this->source); })()), "session", [], "any", false, false, false, 5702), "get", ["paymentmoniteco"], "method", false, false, false, 5702) == "succses")) {
// line 5703
yield "
Swal.fire({
icon: 'success',
title: ' succès ',
text: 'votre commande est validée'
});
";
}
// line 5713
yield " ";
}
// line 5714
yield "
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();
";
// line 5788
if ((CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["app"]) || array_key_exists("app", $context) ? $context["app"] : (function () { throw new RuntimeError('Variable "app" does not exist.', 5788, $this->source); })()), "user", [], "any", false, false, false, 5788), "showpubprod", [], "any", false, false, false, 5788) != "false")) {
// line 5789
yield " \$('#btnPubProd').click();
\$('.modal-backdrop').css('background-color', 'rgba(0, 0, 0, 0.2)');
";
}
// line 5792
yield " });
\$(\"#closeImage\").click(function () {
\$('#myModalImage').css('display', \"none\");
});
\$.ajax({
type: \"POST\",
url: \"";
// line 5798
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("delateSession_parent");
yield "\",
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 = `";
// line 6354
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("EditionAlbum");
yield "?favorites=\${favoriteCount}`;
break;
case 'digital':
orderUrl = `";
// line 6357
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 15]);
yield "?favorites=\${favoriteCount}`;
break;
case 'prints':
orderUrl = `";
// line 6360
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("AjoutPochettePhotos_Favoris", ["nbr" => 12]);
yield "?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 = `";
// line 6399
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("EditionAlbum");
yield "?favorites=\${favoriteCount}`;
break;
case 'digital':
testUrl = `";
// line 6402
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 15]);
yield "?favorites=\${favoriteCount}`;
break;
case 'prints':
testUrl = `";
// line 6405
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("AjoutPochettePhotos_Favoris", ["nbr" => 12]);
yield "?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
";
// line 6601
if (((isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 6601, $this->source); })()) && CoreExtension::getAttribute($this->env, $this->source, ($context["sejour"] ?? null), "dateFinSejour", [], "any", true, true, false, 6601))) {
// line 6602
yield " window.setSejourEndDate('";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 6602, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 6602), "Y-m-d"), "html", null, true);
yield "');
";
}
// line 6604
yield "
// 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>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
/**
* @codeCoverageIgnore
*/
public function getTemplateName(): string
{
return "Parent/DetailsSejour.html.twig";
}
/**
* @codeCoverageIgnore
*/
public function isTraitable(): bool
{
return false;
}
/**
* @codeCoverageIgnore
*/
public function getDebugInfo(): array
{
return array ( 7309 => 6604, 7303 => 6602, 7301 => 6601, 7102 => 6405, 7096 => 6402, 7090 => 6399, 7048 => 6360, 7042 => 6357, 7036 => 6354, 6477 => 5798, 6469 => 5792, 6464 => 5789, 6462 => 5788, 6386 => 5714, 6383 => 5713, 6371 => 5703, 6368 => 5702, 6366 => 5701, 6232 => 5570, 6228 => 5569, 5950 => 5294, 5873 => 5220, 5745 => 5095, 5734 => 5087, 5723 => 5079, 5705 => 5064, 5057 => 4419, 4987 => 4352, 4976 => 4344, 4712 => 4083, 4687 => 4061, 4631 => 4008, 4495 => 3875, 4434 => 3817, 4422 => 3808, 4171 => 3561, 4158 => 3560, 4144 => 3557, 4131 => 3547, 4122 => 3541, 4111 => 3533, 4104 => 3528, 4102 => 3527, 1511 => 938, 1474 => 903, 1471 => 902, 1457 => 901, 1451 => 897, 1447 => 895, 1443 => 893, 1437 => 892, 1433 => 890, 1426 => 886, 1422 => 884, 1420 => 883, 1413 => 879, 1407 => 875, 1404 => 874, 1400 => 873, 1393 => 868, 1391 => 867, 1389 => 866, 1385 => 864, 1379 => 863, 1375 => 861, 1368 => 857, 1364 => 855, 1362 => 854, 1355 => 850, 1349 => 846, 1346 => 845, 1342 => 844, 1339 => 843, 1337 => 842, 1335 => 841, 1331 => 839, 1325 => 838, 1321 => 836, 1314 => 832, 1310 => 830, 1308 => 829, 1301 => 825, 1295 => 821, 1292 => 820, 1288 => 819, 1285 => 818, 1283 => 817, 1281 => 816, 1275 => 813, 1269 => 809, 1267 => 808, 1262 => 805, 1254 => 802, 1250 => 801, 1244 => 799, 1242 => 798, 1234 => 793, 1226 => 788, 1223 => 787, 1221 => 786, 1216 => 783, 1209 => 779, 1203 => 777, 1201 => 776, 1185 => 762, 1182 => 761, 1178 => 760, 1170 => 754, 1166 => 753, 1161 => 751, 1157 => 750, 1153 => 749, 1149 => 748, 1145 => 747, 1135 => 740, 1127 => 737, 1120 => 733, 1117 => 732, 1114 => 731, 1110 => 730, 1093 => 715, 1087 => 714, 1081 => 713, 1079 => 712, 1074 => 711, 1065 => 705, 1057 => 700, 1049 => 695, 1033 => 682, 1013 => 664, 1007 => 661, 1004 => 660, 1002 => 659, 999 => 658, 993 => 655, 990 => 654, 988 => 653, 985 => 652, 979 => 649, 976 => 648, 974 => 647, 963 => 638, 961 => 630, 926 => 600, 922 => 599, 919 => 598, 916 => 597, 913 => 596, 910 => 595, 907 => 594, 904 => 593, 886 => 592, 883 => 591, 881 => 590, 864 => 575, 850 => 574, 844 => 570, 838 => 568, 835 => 567, 830 => 565, 825 => 562, 822 => 561, 816 => 559, 814 => 558, 804 => 550, 798 => 546, 796 => 545, 793 => 544, 791 => 543, 788 => 542, 786 => 539, 782 => 537, 780 => 535, 765 => 528, 763 => 524, 758 => 522, 754 => 521, 750 => 520, 742 => 517, 739 => 516, 736 => 515, 733 => 514, 730 => 513, 713 => 512, 692 => 494, 666 => 471, 653 => 461, 649 => 460, 645 => 459, 634 => 451, 630 => 450, 625 => 448, 621 => 447, 616 => 445, 612 => 444, 601 => 436, 575 => 413, 548 => 389, 517 => 361, 513 => 360, 358 => 208, 354 => 207, 350 => 206, 305 => 164, 298 => 160, 274 => 139, 267 => 135, 226 => 97, 198 => 72, 162 => 38, 160 => 37, 157 => 35, 144 => 33, 112 => 12, 110 => 11, 106 => 10, 102 => 9, 98 => 8, 94 => 7, 90 => 6, 86 => 5, 80 => 2, 57 => 1, 55 => 33, 42 => 1,);
}
public function getSourceContext(): Source
{
return new Source("{% 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>
.btn-close {
font-size: 1.2rem;
border: none;
background: transparent;
}
.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
%}
{# CRO layers for parent page #}
{% include 'components/parents/_cro_layers.html.twig' with { sejour: sejour, ctx: ctx|default({}) } %}
<!-- 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\" style=\"display:flex; align-items:center; gap:10px;\">Séjour {{ sejour.themSejour }}
<button id=\"croTestAll\" class=\"btn btn-sm btn-outline-secondary d-none\">
Tester CRO
</button>
</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 () {
// Clé localStorage pour tracker la première visite
const sejourId = '{{ sejour.id|default(\"\") }}';
const parentId = '{{ app.user.id|default(\"\") }}';
const sliderKey = `imageSlider_seen_\${parentId}_\${sejourId}`;
const isFirstVisit = !localStorage.getItem(sliderKey);
const sliderContainer = document.querySelector('.divSliderModern');
const imageSlider = document.getElementById('imageSlider');
if (!isFirstVisit) {
// Pas la première visite : cacher le slider
if (sliderContainer) sliderContainer.style.display = 'none';
return; // Ne pas initialiser Splide ni l'autoscroll
}
// Première visite : afficher le slider et initialiser
if (sliderContainer) sliderContainer.style.display = 'block';
// Initialisation du carrousel Splide
var splide = new Splide(\"#imageSlider\", {
type: \"loop\",
perPage: 1,
autoplay: true,
interval: 6000,
pauseOnHover: false,
pauseOnFocus: false,
pagination: false,
arrows: false,
});
splide.mount();
// Fonction pour faire défiler automatiquement vers la section suivante
function scrollToNextSection() {
const targetSection = document.getElementById(\"scrollTarget\");
if (targetSection) {
const targetPosition =
targetSection.getBoundingClientRect().top + window.scrollY;
const adjustedPosition = targetPosition - 50;
window.scrollTo({
top: adjustedPosition,
behavior: \"smooth\",
});
}
}
// Démarrer le timer pour le scroll automatique après 5 secondes (première visite uniquement)
setTimeout(scrollToNextSection, 5000);
// Marquer comme vu après le premier scroll
setTimeout(() => {
localStorage.setItem(sliderKey, '1');
}, 6000);
});
</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 %}
", "Parent/DetailsSejour.html.twig", "/var/www/5sur5sejour/templates/Parent/DetailsSejour.html.twig");
}
}