<?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 1762
$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.', 1762, $this->source); })()), "session", [], "any", false, false, false, 1762), "get", ["pageMenu"], "method", false, false, false, 1762);
// 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 "\" />
<link rel=\"stylesheet\" href=\"";
// line 11
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("css/sidebar-ecommerce-pro.css"), "html", null, true);
yield "\">
";
// line 12
$context["destination"] = "detailsejour";
// line 13
yield "
";
// line 15
$context["nowTs"] = $this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "U");
// line 16
$context["debutTs"] = $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.', 16, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 16), "U");
// line 17
$context["finTs"] = $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.', 17, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 17), "U");
// line 18
$context["closeTs"] = (((($tmp = 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.', 18, $this->source); })()), "dateFinCode", [], "any", false, false, false, 18)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($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.', 18, $this->source); })()), "dateFinCode", [], "any", false, false, false, 18), "U")) : ((isset($context["finTs"]) || array_key_exists("finTs", $context) ? $context["finTs"] : (function () { throw new RuntimeError('Variable "finTs" does not exist.', 18, $this->source); })())));
// line 19
$context["secondsPerDay"] = 86400;
// line 20
$context["daysSinceStart"] = Twig\Extension\CoreExtension::round((((isset($context["nowTs"]) || array_key_exists("nowTs", $context) ? $context["nowTs"] : (function () { throw new RuntimeError('Variable "nowTs" does not exist.', 20, $this->source); })()) - (isset($context["debutTs"]) || array_key_exists("debutTs", $context) ? $context["debutTs"] : (function () { throw new RuntimeError('Variable "debutTs" does not exist.', 20, $this->source); })())) / (isset($context["secondsPerDay"]) || array_key_exists("secondsPerDay", $context) ? $context["secondsPerDay"] : (function () { throw new RuntimeError('Variable "secondsPerDay" does not exist.', 20, $this->source); })())), 0, "floor");
// line 21
$context["daysUntilEnd"] = Twig\Extension\CoreExtension::round((((isset($context["finTs"]) || array_key_exists("finTs", $context) ? $context["finTs"] : (function () { throw new RuntimeError('Variable "finTs" does not exist.', 21, $this->source); })()) - (isset($context["nowTs"]) || array_key_exists("nowTs", $context) ? $context["nowTs"] : (function () { throw new RuntimeError('Variable "nowTs" does not exist.', 21, $this->source); })())) / (isset($context["secondsPerDay"]) || array_key_exists("secondsPerDay", $context) ? $context["secondsPerDay"] : (function () { throw new RuntimeError('Variable "secondsPerDay" does not exist.', 21, $this->source); })())), 0, "ceil");
// line 22
$context["daysAfterEnd"] = Twig\Extension\CoreExtension::round((((isset($context["nowTs"]) || array_key_exists("nowTs", $context) ? $context["nowTs"] : (function () { throw new RuntimeError('Variable "nowTs" does not exist.', 22, $this->source); })()) - (isset($context["finTs"]) || array_key_exists("finTs", $context) ? $context["finTs"] : (function () { throw new RuntimeError('Variable "finTs" does not exist.', 22, $this->source); })())) / (isset($context["secondsPerDay"]) || array_key_exists("secondsPerDay", $context) ? $context["secondsPerDay"] : (function () { throw new RuntimeError('Variable "secondsPerDay" does not exist.', 22, $this->source); })())), 0, "floor");
// line 23
$context["daysUntilClose"] = Twig\Extension\CoreExtension::round((((isset($context["closeTs"]) || array_key_exists("closeTs", $context) ? $context["closeTs"] : (function () { throw new RuntimeError('Variable "closeTs" does not exist.', 23, $this->source); })()) - (isset($context["nowTs"]) || array_key_exists("nowTs", $context) ? $context["nowTs"] : (function () { throw new RuntimeError('Variable "nowTs" does not exist.', 23, $this->source); })())) / (isset($context["secondsPerDay"]) || array_key_exists("secondsPerDay", $context) ? $context["secondsPerDay"] : (function () { throw new RuntimeError('Variable "secondsPerDay" does not exist.', 23, $this->source); })())), 0, "ceil");
// line 24
yield "<style>
/* ============================================
PREMIUM DAY CARDS - Style Accompagnateur
============================================ */
/* Base card styles */
/* ============================================
PREMIUM DAY CARDS - Timeline ultra-premium
Identique à l'espace accompagnateur
============================================ */
.date-card {
flex: 0 0 auto;
width: 110px;
background: #fff;
border-radius: 14px;
padding: 8px 6px;
text-align: center;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
scroll-snap-align: center;
position: relative;
min-height: 56px;
max-height: 68px;
border: 1px solid transparent;
cursor: pointer;
}
.date-card .day {
font-weight: 600;
font-size: 0.70rem;
color: #2c3e50;
margin-bottom: 1px;
letter-spacing: -0.01em;
line-height: 1.15;
}
.date-card .full-date {
font-size: 0.60rem;
color: #6b7280;
font-weight: 500;
line-height: 1.1;
margin-top: 0px;
padding-bottom: 5px;
}
.date-card ul {
margin: 4px 0 0 0;
padding: 0;
list-style: none;
display: flex;
gap: 4px;
justify-content: center;
flex-wrap: wrap;
align-items: center;
}
.date-card ul li {
font-size: 0.60rem;
color: #6b7280;
display: flex;
align-items: center;
gap: 2px;
line-height: 1;
font-weight: 500;
}
.date-card ul li i {
font-size: 0.65rem;
}
.premium-day-card {
flex: 0 0 auto;
width: 130px;
background: #fff;
border-radius: 14px;
padding: 8px 6px;
text-align: center;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
scroll-snap-align: center;
position: relative;
min-height: 56px;
max-height: 68px;
border: 1px solid transparent;
cursor: pointer;
will-change: transform;
contain: layout style;
}
.premium-day-card .day {
font-weight: 600;
font-size: 0.9rem;
color: #2c3e50;
margin-bottom: 1px;
letter-spacing: -0.01em;
line-height: 1.15;
}
.premium-day-card .full-date {
font-size: 0.75rem;
color: #6b7280;
font-weight: 500;
line-height: 1.1;
margin-top: 0;
}
/* Compteurs de médias - Points colorés minimalistes */
.premium-day-card ul,
.date-card ul,
.media-list-horizontal {
margin: 4px 0 0 0;
padding: 0;
list-style: none;
display: flex;
gap: 4px;
justify-content: center;
flex-wrap: wrap;
align-items: center;
}
.premium-day-card ul li,
.date-card ul li {
font-size: 0.60rem;
color: #6b7280;
display: flex;
align-items: center;
gap: 2px;
line-height: 1;
font-weight: 500;
}
.premium-day-card ul li i,
.date-card ul li i {
font-size: 0.65rem;
}
/* ============================================
COULEURS PAR TYPE DE MÉDIA - Uniforme pour toutes les cartes
Photo = Orange, Vocal = Bleu, Vidéo = Orange clair
============================================ */
/* Photo = Orange 🟠 */
.premium-day-card ul li i.bi-camera,
.premium-day-card ul li i.bi-camera-fill,
.premium-day-card ul li i.bi-image,
.premium-day-card ul li i.bi-images,
.premium-day-past ul li i.bi-camera,
.premium-day-past ul li i.bi-camera-fill,
.premium-day-past ul li i.bi-image,
.premium-day-past ul li i.bi-images,
.premium-day-current ul li i.bi-camera,
.premium-day-current ul li i.bi-camera-fill,
.premium-day-current ul li i.bi-image,
.premium-day-current ul li i.bi-images,
.premium-day-future ul li i.bi-camera,
.premium-day-future ul li i.bi-camera-fill,
.premium-day-future ul li i.bi-image,
.premium-day-future ul li i.bi-images,
.date-card ul li i.bi-camera,
.date-card ul li i.bi-camera-fill,
.date-card ul li i.bi-image,
.date-card ul li i.bi-images {
color: #F56040 !important;
opacity: 1 !important;
}
/* Vocal/Audio = Bleu Mint 🔵 */
.premium-day-card ul li i.bi-mic-fill,
.premium-day-card ul li i.bi-mic,
.premium-day-past ul li i.bi-mic-fill,
.premium-day-past ul li i.bi-mic,
.premium-day-current ul li i.bi-mic-fill,
.premium-day-current ul li i.bi-mic,
.premium-day-future ul li i.bi-mic-fill,
.premium-day-future ul li i.bi-mic,
.date-card ul li i.bi-mic-fill,
.date-card ul li i.bi-mic {
color: #41A2AA !important;
opacity: 1 !important;
}
/* Vidéo = Orange clair 🟠 */
.premium-day-card ul li i.bi-camera-video,
.premium-day-card ul li i.bi-camera-video-fill,
.premium-day-card ul li i.bi-film,
.premium-day-past ul li i.bi-camera-video,
.premium-day-past ul li i.bi-camera-video-fill,
.premium-day-past ul li i.bi-film,
.premium-day-current ul li i.bi-camera-video,
.premium-day-current ul li i.bi-camera-video-fill,
.premium-day-current ul li i.bi-film,
.premium-day-future ul li i.bi-camera-video,
.premium-day-future ul li i.bi-camera-video-fill,
.premium-day-future ul li i.bi-film,
.date-card ul li i.bi-camera-video,
.date-card ul li i.bi-camera-video-fill,
.date-card ul li i.bi-film {
color: #FF9F7A !important;
opacity: 1 !important;
}
/* État PASSÉ - Cohérent avec charte 5sur5 */
.premium-day-past {
opacity: 0.85;
background: rgba(65, 162, 170, 0.05);
border: 1px solid rgba(65, 162, 170, 0.25);
cursor: pointer;
pointer-events: auto;
}
.premium-day-past:hover {
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
opacity: 1;
}
/* État AUJOURD'HUI - Premium mis en avant clairement */
.premium-day-current {
width: 110px !important;
border: 2px solid #41A2AA !important;
background: #fff !important;
box-shadow: 0 6px 16px rgba(65, 162, 170, 0.12) !important;
transform: scale(1.04);
z-index: 10;
cursor: pointer;
opacity: 1 !important;
position: relative;
}
/* Point animé \"Aujourd'hui\" - style calendrier moderne */
.premium-day-current::after {
content: '';
position: absolute;
top: 8px;
right: 8px;
width: 8px;
height: 8px;
background: #41A2AA;
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0.7);
animation: todayPulse 2s ease-in-out infinite;
z-index: 5;
}
@keyframes todayPulse {
0% {
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0.7);
}
50% {
box-shadow: 0 0 0 6px rgba(65, 162, 170, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0);
}
}
.premium-day-current:hover {
transform: scale(1.04);
box-shadow: 0 6px 16px rgba(65, 162, 170, 0.12) !important;
}
.premium-day-current:focus-visible {
outline: 3px solid rgba(65, 162, 170, 0.3);
outline-offset: 2px;
}
.premium-day-current .day,
.premium-day-current .full-date {
color: #137F86 !important;
font-weight: 600 !important;
}
.premium-day-current .full-date {
padding-bottom: 5px !important;
}
.premium-day-current ul li {
color: #137F86 !important;
}
/* État ACTIF - Carte sélectionnée */
.date-card.active,
.premium-day-card.active {
width: 140px !important;
border: 2px solid #41A2AA !important;
background: #EAF7F7 !important;
box-shadow: 0 2px 8px rgba(65, 162, 170, 0.15) !important;
transform: scale(1.02);
z-index: 10;
}
.date-card.active .day,
.premium-day-card.active .day,
.date-card.active .day-number,
.premium-day-card.active .day-number {
color: #137F86 !important;
font-weight: 700;
}
.date-card.active .full-date,
.premium-day-card.active .full-date,
.date-card.active .day-name,
.premium-day-card.active .day-name {
color: #137F86 !important;
font-weight: 700;
}
.date-card.active ul li,
.premium-day-card.active ul li {
color: #137F86 !important;
}
.date-card.active ul li i,
.premium-day-card.active ul li i {
color: #41A2AA !important;
}
.date-card:hover,
.premium-day-card:hover {
background-color: #f9f9f9;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* État FUTUR - Semi-caché, réduit, verrouillé */
.premium-day-future {
opacity: 0.45 !important;
transform: scale(0.92);
cursor: not-allowed !important;
pointer-events: none !important;
border: 1px solid rgba(188, 196, 206, 0.2) !important;
background: #fff !important;
box-shadow: none !important;
}
.premium-day-future:hover {
transform: scale(0.92) !important;
box-shadow: none !important;
}
.premium-day-future .day,
.premium-day-future .full-date {
color: #BCC4CE !important;
}
.premium-day-future ul {
display: none !important;
}
/* Icône cadenas sur jours futurs */
.premium-day-future::before {
content: '\\F4C0';
font-family: 'bootstrap-icons';
position: absolute;
top: 4px;
right: 4px;
font-size: 9px;
color: #BCC4CE;
opacity: 0.25;
}
/* Tooltip pour jours futurs */
.premium-day-future::after {
content: attr(data-tooltip);
position: absolute;
bottom: -35px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85);
color: white;
padding: 6px 10px;
border-radius: 6px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
z-index: 1000;
}
/* Date container scroll */
.date-container {
display: flex;
gap: 12px;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
padding: 8px 4px;
}
.date-container::-webkit-scrollbar {
display: none;
}
/* Section days wrapper */
.section-days {
background: #f8fafb;
border-radius: 12px;
padding: 10px 12px;
margin-bottom: 16px;
border: 1px solid rgba(65, 162, 170, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* Bouton favoris désactivé (0 favoris) */
.mtb-btn.favoris-empty,
.mtb-btn[data-filter=\"favoris\"]:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
background: rgba(0, 0, 0, 0.02) !important;
border-color: rgba(0, 0, 0, 0.08) !important;
}
.mtb-btn.favoris-empty:hover,
.mtb-btn[data-filter=\"favoris\"]:disabled:hover {
transform: none;
box-shadow: none;
}
.favoris-empty-text {
font-size: 11px;
font-family: inherit;
color: #94a3b8;
font-style: italic;
font-weight: 500;
line-height: 1.2;
}
/* Styles premium pour les filtres */
.premium-5sur5-actions-group {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.premium-5sur5-action-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 12px;
background: #F7FBFC;
border: 1px solid rgba(65, 162, 170, 0.12);
border-radius: 14px;
color: #41A2AA;
font-size: 13px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
position: relative;
line-height: 1.4;
}
.premium-5sur5-action-btn:hover:not(:disabled) {
background: rgba(65, 162, 170, 0.08);
border-color: rgba(65, 162, 170, 0.25);
transform: translateY(-1px);
}
.premium-5sur5-action-btn:active:not(:disabled) {
transform: translateY(0);
}
.premium-5sur5-action-btn i {
font-size: 14px;
opacity: 0.85;
line-height: 1;
}
.premium-5sur5-action-btn span:not(.premium-5sur5-badge):not(.favoris-empty-text) {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.2px;
line-height: 1.4;
font-family: inherit;
}
.premium-5sur5-badge {
display: inline-block;
font-size: 11px;
font-weight: 600;
font-family: inherit;
padding: 2px 6px;
border-radius: 6px;
letter-spacing: 0.2px;
margin-left: 2px;
line-height: 1.2;
}
/* État actif pour les boutons premium */
.premium-5sur5-action-btn.mtb-btn.active,
.premium-5sur5-action-btn.mtb-btn.is-active {
background: rgba(65, 162, 170, 0.15) !important;
border-color: #41A2AA !important;
color: #137F86 !important;
box-shadow: 0 2px 8px rgba(65, 162, 170, 0.2);
}
.premium-5sur5-action-btn.mtb-btn[data-filter=\"favoris\"].active {
background: rgba(245, 96, 64, 0.15) !important;
border-color: #f56040 !important;
color: #d43e1f !important;
}
.premium-5sur5-action-btn.mtb-btn[data-filter=\"audio\"].active {
background: rgba(255, 215, 0, 0.15) !important;
border-color: #ffd700 !important;
color: #b8860b !important;
}
/* Bouton favoris désactivé */
.premium-5sur5-action-btn.favoris-empty,
.premium-5sur5-action-btn[data-filter=\"favoris\"]:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
background: rgba(0, 0, 0, 0.02) !important;
border-color: rgba(0, 0, 0, 0.08) !important;
}
.premium-5sur5-action-btn.favoris-empty:hover,
.premium-5sur5-action-btn[data-filter=\"favoris\"]:disabled:hover {
transform: none;
box-shadow: none;
}
/* Styles pour la section favoris */
.favorites-ecommerce-view {
width: 100%;
max-width: 100%;
padding: 24px;
box-sizing: border-box;
}
.favorites-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 20px;
background: #f8fafb;
border-radius: 12px;
border: 1px solid rgba(65, 162, 170, 0.1);
}
.favorites-title {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.favorites-title h3 {
font-size: 20px;
font-weight: 700;
color: #1a1a1a;
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.favorites-title h3 i {
color: #f56040;
font-size: 20px;
}
.favorites-count {
display: inline-flex;
align-items: center;
padding: 6px 12px;
background: rgba(245, 96, 64, 0.1);
color: #f56040;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
}
.btn-back-to-days {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #ffffff;
border: 1px solid rgba(65, 162, 170, 0.2);
border-radius: 10px;
color: #6b7280;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-back-to-days:hover {
background: rgba(65, 162, 170, 0.05);
border-color: #41A2AA;
color: #41A2AA;
}
.favorites-content {
width: 100%;
}
.favorites-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
width: 100%;
padding: 0;
}
.favorites-grid .favorite-item,
.favorites-grid .photo-item {
position: relative;
width: 100%;
aspect-ratio: 1;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
cursor: pointer;
}
.favorites-grid .favorite-item:hover,
.favorites-grid .photo-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.favorites-grid .favorite-item:hover .favorite-actions,
.favorites-grid .photo-item:hover .favorite-actions {
opacity: 1;
}
.favorites-grid img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.favorites-grid .favorite-item {
position: relative;
}
.favorites-grid .favorite-actions {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 6px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 10;
}
/* CTA discret et premium */
.premium-cta-subtle {
margin: 20px 0;
padding: 16px 20px;
background: rgba(65, 162, 170, 0.04);
border: 1px solid rgba(65, 162, 170, 0.15);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
text-decoration: none;
color: inherit;
}
.premium-cta-subtle:hover {
background: rgba(65, 162, 170, 0.08);
border-color: rgba(65, 162, 170, 0.25);
transform: translateX(2px);
text-decoration: none;
color: inherit;
}
.premium-cta-subtle > div:first-child {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.premium-cta-subtle > div:first-child > div:first-child {
width: 40px;
height: 40px;
background: rgba(65, 162, 170, 0.1);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.premium-cta-subtle > div:first-child > div:first-child i {
font-size: 18px;
color: #41A2AA;
}
.premium-cta-subtle > div:first-child > div:last-child {
flex: 1;
min-width: 0;
}
.premium-cta-subtle > div:first-child > div:last-child p:first-child {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
}
.premium-cta-subtle > div:first-child > div:last-child p:last-child {
margin: 4px 0 0 0;
font-size: 12px;
color: #6b7280;
line-height: 1.4;
}
.premium-cta-subtle > i:last-child {
font-size: 18px;
color: #41A2AA;
flex-shrink: 0;
}
.premium-5sur5-day-title {
font-size: 1.35rem;
font-weight: 700;
color: #2c3e50;
margin: 0 0 4px 0;
letter-spacing: -0.02em;
line-height: 1.3;
}
.premium-5sur5-day-subtitle {
font-size: 0.9rem;
color: #6b7280;
margin: 0;
font-weight: 500;
line-height: 1.5;
}
@media (max-width: 768px) {
.premium-5sur5-day-header {
padding: 12px 16px;
margin-bottom: 16px;
}
.premium-5sur5-day-title {
font-size: 1.15rem;
}
.premium-5sur5-day-subtitle {
font-size: 0.8rem;
}
/* Menu sticky en bas - Override des styles précédents */
.premium-5sur5-actions-group {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
background: #ffffff !important;
border-top: 1px solid rgba(65, 162, 170, 0.15) !important;
padding: 12px 16px !important;
z-index: 1050 !important;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08) !important;
justify-content: space-around !important;
gap: 0 !important;
margin: 0 !important;
margin-top: 0 !important;
/* Safe area pour iPhone avec encoche */
padding-bottom: max(12px, env(safe-area-inset-bottom)) !important;
}
.premium-5sur5-action-btn {
flex: 1 !important;
justify-content: center !important;
min-width: 0 !important;
padding: 10px 8px !important;
border-radius: 12px !important;
font-size: 12px !important;
}
.premium-5sur5-action-btn i {
font-size: 16px !important;
}
.premium-5sur5-action-btn span:not(.premium-5sur5-badge):not(.favoris-empty-text) {
font-size: 12px !important;
}
.premium-5sur5-badge {
font-size: 10px !important;
padding: 2px 5px !important;
}
/* Ajouter un padding-bottom au contenu pour éviter que le menu sticky ne cache le contenu */
#scrollTarget {
padding-bottom: 80px !important;
}
/* Section favoris responsive */
.favorites-ecommerce-view {
padding: 16px !important;
}
.favorites-header {
flex-direction: column !important;
gap: 12px !important;
align-items: flex-start !important;
padding: 16px !important;
margin-bottom: 16px !important;
}
.favorites-title {
width: 100% !important;
}
.favorites-title h3 {
font-size: 18px !important;
margin-bottom: 8px !important;
}
.favorites-count {
font-size: 13px !important;
padding: 4px 10px !important;
}
.btn-back-to-days {
width: 100% !important;
justify-content: center !important;
padding: 10px 16px !important;
font-size: 14px !important;
}
.favorites-content {
padding: 0 !important;
}
.favorites-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 12px !important;
padding: 0 !important;
}
.favorites-grid .favorite-item,
.favorites-grid .photo-item {
width: 100% !important;
height: auto !important;
min-height: 150px !important;
aspect-ratio: 1 !important;
position: relative !important;
display: block !important;
border-radius: 12px !important;
overflow: hidden !important;
background: #f0f0f0 !important;
}
.favorites-grid .favorite-item img,
.favorites-grid .photo-item img {
width: 100% !important;
height: 100% !important;
min-height: 150px !important;
object-fit: cover !important;
display: block !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
border-radius: 12px !important;
}
.favorites-grid video {
width: 100% !important;
height: 100% !important;
min-height: 150px !important;
object-fit: cover !important;
display: block !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
border-radius: 12px !important;
}
/* CTA discret responsive */
.premium-cta-subtle {
padding: 12px 16px !important;
margin: 16px 0 !important;
}
.premium-cta-subtle > div:first-child > div:first-child {
width: 36px !important;
height: 36px !important;
}
.premium-cta-subtle > div:first-child > div:first-child i {
font-size: 16px !important;
}
.premium-cta-subtle > div:first-child > div:last-child p:first-child {
font-size: 13px !important;
}
.premium-cta-subtle > div:first-child > div:last-child p:last-child {
font-size: 11px !important;
}
.premium-cta-subtle:hover {
background: rgba(65, 162, 170, 0.08) !important;
border-color: rgba(65, 162, 170, 0.25) !important;
transform: translateX(2px) !important;
}
}
.date-navigation {
width: 100%;
}
/* Legacy compatibility */
.date-card .card-content {
display: block;
text-align: center;
}
.date-card .media-list-horizontal {
margin: 0 !important;
padding: 0 !important;
display: flex;
gap: 6px;
justify-content: center;
}
.date-card .media-list-horizontal li {
font-size: 0.65rem !important;
}
/* Dots minimalistes pour les indicateurs de jour */
.dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-left: 4px;
}
.dot.blue {
background: #41a2aa;
}
.dot.green {
background: #8ed081;
}
.dot.orange {
background: #ff9c57;
}
/* Dot multi-couleur pour aujourd'hui + premier/dernier jour */
.status-dot.multi {
position: absolute;
top: 6px;
right: 6px;
width: 10px;
height: 10px;
border-radius: 50%;
background: linear-gradient(90deg, #41a2aa 50%, #ff9c57 50%);
z-index: 5;
}
/* Variante pour premier jour + aujourd'hui */
.status-dot.multi.green-orange {
background: linear-gradient(90deg, #8ed081 50%, #ff9c57 50%);
}
/* ==================== TOASTS & ANIMATIONS ==================== */
@keyframes slideUpFromGift {
0% {
opacity: 0;
transform: translateY(60px) scale(0.9);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
/* ========== BANDEAU PROMO SÉJOUR TERMINÉ ========== */
.promo-banner-termine {
background: linear-gradient(90deg, #d32f2f 0%, #e65100 50%, #ff5722 100%);
color: white;
padding: 0;
height: 38px;
width: 100%;
position: relative;
z-index: 100;
box-shadow: 0 2px 6px rgba(211, 47, 47, 0.2);
}
.promo-banner-content {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
max-width: 1200px;
margin: 0 auto;
padding: 0 12px;
height: 100%;
}
.promo-banner-icon {
font-size: 16px;
flex-shrink: 0;
}
.promo-banner-text {
font-size: 12px;
line-height: 1.2;
}
.promo-banner-text strong {
font-weight: 700;
display: inline;
}
.promo-days {
color: #fff;
font-weight: 700;
background: rgba(255,255,255,0.2);
padding: 1px 5px;
border-radius: 3px;
font-size: 11px;
}
.promo-banner-cta {
background: #fff;
color: #d32f2f;
font-weight: 600;
font-size: 11px;
padding: 4px 12px;
border-radius: 12px;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s;
}
.promo-banner-cta:hover {
background: rgba(255,255,255,0.9);
transform: scale(1.02);
color: #d32f2f;
}
.promo-banner-close {
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
opacity: 0.7;
padding: 0 2px;
margin-left: 2px;
line-height: 1;
}
.promo-banner-close:hover {
opacity: 1;
}
@media (max-width: 600px) {
.promo-banner-termine {
height: 34px;
}
.promo-banner-content {
gap: 6px;
padding: 0 8px;
}
.promo-banner-text {
font-size: 11px;
}
.promo-banner-icon {
font-size: 14px;
}
}
/* ========== SLIDER COMPACT PREMIUM ========== */
#heroPromo {
margin: 0;
padding: 0;
width: 100%;
overflow: hidden;
background: #fff;
}
#heroPromo .divSliderModern {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider {
max-height: 70px !important;
height: 70px !important;
overflow: hidden !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__track {
height: 70px !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__list {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__slide {
height: 70px !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__slide img {
object-fit: contain !important;
height: auto !important;
width: auto !important;
}
.slider-content-compact {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0;
background: linear-gradient(135deg, #f7fcfc 0%, #ffffff 30%, #fef8f5 100%);
height: 70px !important;
padding: 0;
width: 100%;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
object-fit: cover;
flex-shrink: 0;
display: block;
}
.slider-text-compact {
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-start;
justify-content: center;
flex: 1;
min-width: 0;
overflow: hidden;
padding: 12px 24px;
height: 100%;
}
.slider-title-compact {
font-size: 14px;
font-weight: 700;
line-height: 1.3;
color: inherit;
white-space: normal;
word-wrap: break-word;
max-width: 100%;
}
.slider-subtitle-compact {
font-size: 12px;
font-weight: 500;
line-height: 1.3;
color: inherit;
white-space: normal;
word-wrap: break-word;
max-width: 100%;
}
@media (max-width: 768px) {
#imageSlider, #imageSlider .splide__track, #imageSlider .splide__slide, .slider-content-compact {
height: 65px !important;
}
.slider-content-compact {
gap: 0;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
}
.slider-text-compact {
padding: 10px 16px;
}
.slider-title-compact {
font-size: 13px;
line-height: 1.2;
}
.slider-subtitle-compact {
font-size: 11px;
line-height: 1.2;
}
}
@media (max-width: 480px) {
#imageSlider, #imageSlider .splide__track, #imageSlider .splide__slide, .slider-content-compact {
height: 60px !important;
}
.slider-content-compact {
gap: 0;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
}
.slider-text-compact {
padding: 8px 12px;
}
.slider-title-compact {
font-size: 12px;
line-height: 1.2;
}
.slider-subtitle-compact {
font-size: 10px;
line-height: 1.2;
}
}
/* ========== EMPTY STATE ========== */
.stay-empty-wrapper {
padding: 50px 20px 60px;
display: flex;
justify-content: center;
}
.stay-empty-card {
max-width: 720px;
width: 100%;
background: #ffffff;
border-radius: 24px;
padding: 44px 40px 40px;
box-shadow: 0 16px 50px rgba(5, 45, 60, 0.06);
margin: 0 auto;
}
.stay-empty-title {
font-size: 1.5rem;
font-weight: 700;
color: #1A1A2E;
margin-bottom: 20px;
text-align: center;
}
.stay-empty-intro {
font-size: 0.98rem;
line-height: 1.7;
color: #5A6178;
max-width: 580px;
margin: 0 auto 32px;
text-align: center;
}
/* Section \"Ce qui va arriver\" */
.stay-empty-expect {
display: flex;
gap: 36px;
align-items: flex-start;
margin-bottom: 28px;
}
.stay-empty-expect-content {
flex: 1;
}
.stay-empty-expect-label {
font-size: 0.92rem;
font-weight: 600;
color: #1A1A2E;
margin-bottom: 16px;
}
.stay-empty-list {
list-style: none;
padding: 0;
margin: 0;
}
.stay-empty-list li {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 0;
font-size: 0.92rem;
color: #4B5563;
line-height: 1.5;
}
.stay-empty-list-icon {
width: 28px;
height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
}
.stay-empty-list-icon svg {
width: 14px;
height: 14px;
}
.stay-empty-list-icon--check {
background: rgba(65, 162, 170, 0.12);
color: #41A2AA;
}
.stay-empty-list-icon--photos {
background: rgba(240, 158, 122, 0.12);
color: #E8865E;
}
.stay-empty-list-icon--vocal {
background: rgba(155, 139, 244, 0.12);
color: #7B6BE0;
}
/* Illustration compacte */
.stay-empty-illustration {
position: relative;
width: 180px;
height: 200px;
flex-shrink: 0;
}
.stay-empty-phone {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 90px;
filter: drop-shadow(0 8px 20px rgba(0,0,0,0.1));
animation: phoneFloat 4s ease-in-out infinite;
}
@keyframes phoneFloat {
0%, 100% { transform: translate(-50%, -50%) translateY(0); }
50% { transform: translate(-50%, -50%) translateY(-5px); }
}
.stay-empty-bubble {
position: absolute;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 14px rgba(0,0,0,0.08);
font-size: 0.72rem;
font-weight: 600;
color: #1A1A2E;
white-space: nowrap;
opacity: 0;
animation: bubbleFadeIn 0.5s ease forwards;
}
.stay-empty-bubble-icon {
width: 18px;
height: 18px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stay-empty-bubble-icon svg {
width: 10px;
height: 10px;
}
.stay-empty-bubble--arrived {
top: 20px;
left: -10px;
animation-delay: 0.4s;
}
.stay-empty-bubble--arrived .stay-empty-bubble-icon {
background: linear-gradient(135deg, #41A2AA, #359BA3);
color: white;
}
.stay-empty-bubble--photos {
top: 70px;
right: -5px;
animation-delay: 0.9s;
}
.stay-empty-bubble--photos .stay-empty-bubble-icon {
background: linear-gradient(135deg, #F09E7A, #E8865E);
color: white;
}
@keyframes bubbleFadeIn {
0% { opacity: 0; transform: translateY(6px) scale(0.95); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
.stay-empty-illustration-label {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
color: #8E95A9;
white-space: nowrap;
text-align: center;
}
/* Note rassurante */
.stay-empty-reassurance {
background: #F8FAFB;
border-radius: 12px;
padding: 14px 20px;
margin-bottom: 24px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.stay-empty-reassurance-icon {
width: 20px;
height: 20px;
color: #41A2AA;
flex-shrink: 0;
margin-top: 2px;
}
.stay-empty-reassurance p {
margin: 0;
font-size: 0.85rem;
color: #5A6178;
line-height: 1.6;
}
/* CTA */
.stay-empty-cta {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 22px;
font-size: 0.88rem;
font-weight: 600;
color: #41A2AA;
background: transparent;
border: 1.5px solid #E5E9F0;
border-radius: 10px;
text-decoration: none;
transition: all 0.2s ease;
}
.stay-empty-cta:hover {
background: #F0F9FA;
border-color: #41A2AA;
color: #359BA3;
text-decoration: none;
}
.stay-empty-cta svg {
width: 16px;
height: 16px;
}
.stay-empty-footer {
text-align: center;
}
/* Responsive */
@media (max-width: 700px) {
.stay-empty-card {
padding: 32px 24px 32px;
}
.stay-empty-title {
font-size: 1.3rem;
}
.stay-empty-expect {
flex-direction: column;
gap: 24px;
}
.stay-empty-illustration {
width: 100%;
height: 160px;
order: -1;
}
.stay-empty-phone {
width: 80px;
}
.stay-empty-bubble {
font-size: 0.68rem;
padding: 5px 8px;
}
.stay-empty-bubble--arrived {
left: calc(50% - 85px);
}
.stay-empty-bubble--photos {
right: calc(50% - 85px);
}
.stay-empty-reassurance {
flex-direction: column;
gap: 8px;
text-align: center;
}
.stay-empty-reassurance-icon {
margin: 0 auto;
}
}
</style>
<script>
window.__FEATURE_PARENT_CONVERSION__ = true;
// Configuration runtime pour le système B2C
";
// line 1575
yield " ";
$context["nowTimestamp"] = $this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "U");
// line 1576
yield " ";
$context["debutTimestamp"] = $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.', 1576, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 1576), "U");
// line 1577
yield " ";
$context["finTimestamp"] = $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.', 1577, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1577), "U");
// line 1578
yield " ";
$context["finCodeDateJS"] = (((CoreExtension::getAttribute($this->env, $this->source, ($context["sejour"] ?? null), "dateFinCode", [], "any", true, true, false, 1578) && !(null === 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.', 1578, $this->source); })()), "dateFinCode", [], "any", false, false, false, 1578)))) ? (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.', 1578, $this->source); })()), "dateFinCode", [], "any", false, false, false, 1578)) : ($this->extensions['Twig\Extension\CoreExtension']->modifyDate(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.', 1578, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1578), "+42 days")));
// line 1579
yield " ";
$context["closeTimestamp"] = $this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["finCodeDateJS"]) || array_key_exists("finCodeDateJS", $context) ? $context["finCodeDateJS"] : (function () { throw new RuntimeError('Variable "finCodeDateJS" does not exist.', 1579, $this->source); })()), "U");
// line 1580
yield " ";
$context["daysSinceStartCalc"] = Twig\Extension\CoreExtension::round((((isset($context["nowTimestamp"]) || array_key_exists("nowTimestamp", $context) ? $context["nowTimestamp"] : (function () { throw new RuntimeError('Variable "nowTimestamp" does not exist.', 1580, $this->source); })()) - (isset($context["debutTimestamp"]) || array_key_exists("debutTimestamp", $context) ? $context["debutTimestamp"] : (function () { throw new RuntimeError('Variable "debutTimestamp" does not exist.', 1580, $this->source); })())) / 86400), 0, "floor");
// line 1581
yield " ";
$context["daysUntilEndCalc"] = Twig\Extension\CoreExtension::round((((isset($context["finTimestamp"]) || array_key_exists("finTimestamp", $context) ? $context["finTimestamp"] : (function () { throw new RuntimeError('Variable "finTimestamp" does not exist.', 1581, $this->source); })()) - (isset($context["nowTimestamp"]) || array_key_exists("nowTimestamp", $context) ? $context["nowTimestamp"] : (function () { throw new RuntimeError('Variable "nowTimestamp" does not exist.', 1581, $this->source); })())) / 86400), 0, "ceil");
// line 1582
yield " ";
$context["daysAfterEndCalc"] = Twig\Extension\CoreExtension::round((((isset($context["nowTimestamp"]) || array_key_exists("nowTimestamp", $context) ? $context["nowTimestamp"] : (function () { throw new RuntimeError('Variable "nowTimestamp" does not exist.', 1582, $this->source); })()) - (isset($context["finTimestamp"]) || array_key_exists("finTimestamp", $context) ? $context["finTimestamp"] : (function () { throw new RuntimeError('Variable "finTimestamp" does not exist.', 1582, $this->source); })())) / 86400), 0, "floor");
// line 1583
yield " ";
$context["daysUntilCloseCalc"] = Twig\Extension\CoreExtension::round((((isset($context["closeTimestamp"]) || array_key_exists("closeTimestamp", $context) ? $context["closeTimestamp"] : (function () { throw new RuntimeError('Variable "closeTimestamp" does not exist.', 1583, $this->source); })()) - (isset($context["nowTimestamp"]) || array_key_exists("nowTimestamp", $context) ? $context["nowTimestamp"] : (function () { throw new RuntimeError('Variable "nowTimestamp" does not exist.', 1583, $this->source); })())) / 86400), 0, "ceil");
// line 1584
yield "
window.PARENT_CONVERSION_CONFIG = {
user: {
prenom: \"";
// line 1587
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($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, 1587), "prenom", [], "any", true, true, false, 1587)) ? (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.', 1587, $this->source); })()), "user", [], "any", false, false, false, 1587), "prenom", [], "any", false, false, false, 1587), "Parent")) : ("Parent")), "js"), "html", null, true);
yield "\"
},
sejour: {
id: ";
// line 1590
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.', 1590, $this->source); })()), "id", [], "any", false, false, false, 1590), "html", null, true);
yield ",
dateDebut: \"";
// line 1591
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.', 1591, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 1591), "Y-m-d"), "html", null, true);
yield "\",
dateFin: \"";
// line 1592
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.', 1592, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1592), "Y-m-d"), "html", null, true);
yield "\",
dateFinCode: \"";
// line 1593
yield (((($tmp = 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.', 1593, $this->source); })()), "dateFinCode", [], "any", false, false, false, 1593)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($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.', 1593, $this->source); })()), "dateFinCode", [], "any", false, false, false, 1593), "Y-m-d"), "html", null, true)) : (""));
yield "\",
isTermine: ";
// line 1594
yield (((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.', 1594, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1594) < $this->extensions['Twig\Extension\CoreExtension']->convertDate())) ? ("true") : ("false"));
yield "
},
dates: {
aujourdhui: \"";
// line 1597
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "Y-m-d"), "html", null, true);
yield "\",
isFirstDay: ";
// line 1598
yield ((($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.', 1598, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 1598), "Y-m-d") == $this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "Y-m-d"))) ? ("true") : ("false"));
yield ",
isWithin: ";
// line 1599
yield ((((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.', 1599, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 1599) <= $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.', 1599, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1599) >= $this->extensions['Twig\Extension\CoreExtension']->convertDate()))) ? ("true") : ("false"));
yield ",
daysSinceStart: ";
// line 1600
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("daysSinceStartCalc", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["daysSinceStartCalc"]) || array_key_exists("daysSinceStartCalc", $context) ? $context["daysSinceStartCalc"] : (function () { throw new RuntimeError('Variable "daysSinceStartCalc" does not exist.', 1600, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
daysUntilEnd: ";
// line 1601
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("daysUntilEndCalc", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["daysUntilEndCalc"]) || array_key_exists("daysUntilEndCalc", $context) ? $context["daysUntilEndCalc"] : (function () { throw new RuntimeError('Variable "daysUntilEndCalc" does not exist.', 1601, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
daysAfterEnd: ";
// line 1602
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("daysAfterEndCalc", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["daysAfterEndCalc"]) || array_key_exists("daysAfterEndCalc", $context) ? $context["daysAfterEndCalc"] : (function () { throw new RuntimeError('Variable "daysAfterEndCalc" does not exist.', 1602, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
daysUntilClose: ";
// line 1603
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("daysUntilCloseCalc", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["daysUntilCloseCalc"]) || array_key_exists("daysUntilCloseCalc", $context) ? $context["daysUntilCloseCalc"] : (function () { throw new RuntimeError('Variable "daysUntilCloseCalc" does not exist.', 1603, $this->source); })()), 0)) : (0)), "html", null, true);
yield "
},
stats: {
totalDays: ";
// line 1606
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, 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.', 1606, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 1606), "diff", [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.', 1606, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 1606)], "method", false, false, false, 1606), "days", [], "any", false, false, false, 1606), "html", null, true);
yield ",
totalPhotos: ";
// line 1607
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("attachementsCount", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 1607, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
totalMessages: ";
// line 1608
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("nbmessages", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nbmessages"]) || array_key_exists("nbmessages", $context) ? $context["nbmessages"] : (function () { throw new RuntimeError('Variable "nbmessages" does not exist.', 1608, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
todayPhotos: ";
// line 1609
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("photosDuJour", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["photosDuJour"]) || array_key_exists("photosDuJour", $context) ? $context["photosDuJour"] : (function () { throw new RuntimeError('Variable "photosDuJour" does not exist.', 1609, $this->source); })()), 0)) : (0)), "html", null, true);
yield "
},
parent: {
favorites: ";
// line 1612
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 1612, $this->source); })()), 0)) : (0)), "html", null, true);
yield ",
cartCount: 0,
lastVisitAt: \"";
// line 1614
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "Y-m-d H:i:s"), "html", null, true);
yield "\"
}
};
// Catalogue produits (routes réelles 5sur5)
window.PARENT_PRODUCT_CATALOG = {
digi10: { title:\"Pack numérique 10 photos\", badge:\"Petit budget\", url:\"";
// line 1620
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" },
digi25: { title:\"Pack numérique 25 photos\", badge:\"Immédiat\", url:\"";
// line 1621
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" },
retro12: { title:\"Pochette rétro 12 tirages\", badge:\"Cadeau\", url:\"";
// line 1622
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" },
prints20: { title:\"Pochette 20 tirages\", badge:\"Best seller\", url:\"";
// line 1623
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" },
album24: { title:\"Mini-album 24 pages\", badge:\"Parfait début\", url:\"";
// line 1624
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("Album_du_Sejour");
yield "\" },
album48: { title:\"Album Premium 48 pages\", badge:\"Meilleure vente\", url:\"";
// line 1625
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("Album_du_Sejour");
yield "\" },
mix10Poster: { title:\"Pack mixte 10 tirages + poster\", badge:\"Combo\", url:\"";
// line 1626
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" },
bundleAlbumPrints: { title:\"Album + tirages (-15%)\", badge:\"Éco\", url:\"";
// line 1627
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" }
};
</script>
<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;
}
/* === Badges discrets des cartes jours (style accompagnateur avec Bootstrap) === */
/* Les badges utilisent directement les classes Bootstrap: badge, bg-success, bg-info, position-absolute */
/* === Affichage des dates (style accompagnateur exact) === */
.container--gallery.modern {
background: #ffffff;
border-radius: 32px;
padding: 10px;
box-shadow: 0 30px 70px rgba(11, 36, 71, 0.08);
border: 1px solid rgba(65, 162, 170, 0.08);
min-height:1000px;
}
@media (max-width: 768px) {
.container--gallery.modern {
padding: 24px;
min-height:500px;
}
}
.pro-journal {
background: linear-gradient(145deg, #f8fbff 0%, #ffffff 100%);
border-radius: 24px;
padding: 28px;
margin-bottom: 28px;
border: 1px solid rgba(65, 162, 170, 0.08);
box-shadow: 0 18px 45px rgba(11, 36, 71, 0.05);
}
.pro-entry-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 18px;
margin-bottom: 20px;
}
.pro-entry-header .date-chip {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 18px;
border-radius: 999px;
background: rgba(65, 162, 170, 0.08);
color: #1f2933;
font-weight: 600;
font-size: 0.95rem;
}
.gallery-meta-chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.gallery-meta-chips .filter-badge {
background: #ffffff;
border: 1px solid rgba(15, 40, 77, 0.08);
border-radius: 999px;
padding: 8px 14px;
font-size: 0.85rem;
font-weight: 600;
color: #405064;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.gallery-meta-chips .filter-badge.active {
background: #41a2aa;
color: #fff;
border-color: #41a2aa;
box-shadow: 0 8px 20px rgba(65, 162, 170, 0.25);
}
.gallery-grid {
width: 100%;
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 18px;
}
.gallery-grid .column {
margin: 0 !important;
}
.gallery-grid .photo-item,
.gallery-grid .video-item,
.gallery-grid .audio-message-item {
border-radius: 18px;
background: #fff;
border: 1px solid rgba(15, 40, 77, 0.08);
box-shadow: 0 12px 30px rgba(11, 36, 71, 0.08);
}
.gallery-grid .photo-item img {
border-radius: 16px;
height: 220px;
object-fit: cover;
}
.gallery-grid .video-item video,
.gallery-grid .audio-message-item audio {
border-radius: 12px;
}
</style>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 1762
/**
* @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 1764
yield "
<div class=\"main-content\">
<div class=\"row no-margin\">
<!-- Bouton cadeau attractif e-commerce -->
";
// line 1770
$context["showGiftButton"] = (Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1770, $this->source); })()), [])) : ([]))) > 0);
// line 1771
yield " <div
class=\"gift-button\"
id=\"gift-button-trigger\"
data-has-favorites=\"";
// line 1774
yield (((($tmp = (isset($context["showGiftButton"]) || array_key_exists("showGiftButton", $context) ? $context["showGiftButton"] : (function () { throw new RuntimeError('Variable "showGiftButton" does not exist.', 1774, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ("1") : ("0"));
yield "\"
style=\"cursor: pointer; ";
// line 1775
yield (((($tmp = (isset($context["showGiftButton"]) || array_key_exists("showGiftButton", $context) ? $context["showGiftButton"] : (function () { throw new RuntimeError('Variable "showGiftButton" does not exist.', 1775, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ("") : ("display:none;"));
yield "\"
>
<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 1783
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1783, $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 1808
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1808, $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> Les 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\" id=\"album-card\">
<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 1846
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1846, $this->source); })()), [])) : ([]))), "html", null, true);
yield "</span>
</div>
</div>
<p class=\"product-description\">
Album photo personnalisé avec vos <strong id=\"album-count\">";
// line 1850
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1850, $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\" id=\"digital-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 1871
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1871, $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 1875
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1875, $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 class=\"product-favorites-indicator\" title=\"Photos favorites sélectionnées\">
<i class=\"bi bi-heart-fill\" style=\"color: #f56040\"></i>
<span>";
// line 1896
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1896, $this->source); })()), [])) : ([]))), "html", null, true);
yield "</span>
</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é en France – satisfaction garantie ✅</strong>
</div>
</div>
</div>
<div class=\"selection-popover\" id=\"selectionPopover\">
<h4>Votre sélection</h4>
<p>Tirages : ";
// line 1921
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1921, $this->source); })()), [])) : ([]))), "html", null, true);
yield " / 12</p>
<p>Numériques : ";
// line 1922
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1922, $this->source); })()), [])) : ([]))), "html", null, true);
yield " / 15</p>
<p>Album : ";
// line 1923
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ((array_key_exists("likes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["likes"]) || array_key_exists("likes", $context) ? $context["likes"] : (function () { throw new RuntimeError('Variable "likes" does not exist.', 1923, $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>
";
// line 2075
yield " ";
if ((((array_key_exists("attachementsCount", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 2075, $this->source); })()), 0)) : (0)) > 0)) {
// line 2076
yield "
";
// line 2078
yield " ";
$context["isSejourTermine"] = (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.', 2078, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 2078) < $this->extensions['Twig\Extension\CoreExtension']->convertDate());
// line 2079
yield " ";
$context["finCodeDate"] = (((CoreExtension::getAttribute($this->env, $this->source, ($context["sejour"] ?? null), "dateFinCode", [], "any", true, true, false, 2079) && !(null === 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.', 2079, $this->source); })()), "dateFinCode", [], "any", false, false, false, 2079)))) ? (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.', 2079, $this->source); })()), "dateFinCode", [], "any", false, false, false, 2079)) : ($this->extensions['Twig\Extension\CoreExtension']->modifyDate(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.', 2079, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 2079), "+42 days")));
// line 2080
yield " ";
$context["daysUntilClose"] = Twig\Extension\CoreExtension::round((($this->extensions['Twig\Extension\CoreExtension']->formatDate((isset($context["finCodeDate"]) || array_key_exists("finCodeDate", $context) ? $context["finCodeDate"] : (function () { throw new RuntimeError('Variable "finCodeDate" does not exist.', 2080, $this->source); })()), "U") - $this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "U")) / 86400), 0, "ceil");
// line 2081
yield "
<section id=\"heroPromo\" style=\"margin: 0; padding: 0;\">
";
// line 2084
yield " ";
if ((($tmp = (isset($context["isSejourTermine"]) || array_key_exists("isSejourTermine", $context) ? $context["isSejourTermine"] : (function () { throw new RuntimeError('Variable "isSejourTermine" does not exist.', 2084, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2085
yield " <div class=\"promo-banner-termine\" id=\"promoBannerTermine\">
<div class=\"promo-banner-content\">
<span class=\"promo-banner-icon\">🎬</span>
<div class=\"promo-banner-text\">
";
// line 2089
if (((isset($context["daysUntilClose"]) || array_key_exists("daysUntilClose", $context) ? $context["daysUntilClose"] : (function () { throw new RuntimeError('Variable "daysUntilClose" does not exist.', 2089, $this->source); })()) > 0)) {
// line 2090
yield " <strong>Séjour terminé — Dernière chance !</strong>
Plus que <span class=\"promo-days\">";
// line 2091
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["daysUntilClose"]) || array_key_exists("daysUntilClose", $context) ? $context["daysUntilClose"] : (function () { throw new RuntimeError('Variable "daysUntilClose" does not exist.', 2091, $this->source); })()), "html", null, true);
yield "</span> jour";
yield ((((isset($context["daysUntilClose"]) || array_key_exists("daysUntilClose", $context) ? $context["daysUntilClose"] : (function () { throw new RuntimeError('Variable "daysUntilClose" does not exist.', 2091, $this->source); })()) > 1)) ? ("s") : (""));
yield " pour commander vos souvenirs.
";
} else {
// line 2093
yield " <strong>Séjour terminé</strong>
Créez vos souvenirs : albums, tirages, photos numériques...
";
}
// line 2096
yield " </div>
<a href=\"";
// line 2097
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" class=\"promo-banner-cta\">Commander</a>
<button class=\"promo-banner-close\" onclick=\"this.parentElement.parentElement.style.display='none'\" aria-label=\"Fermer\">×</button>
</div>
</div>
";
} else {
// line 2102
yield " ";
// line 2103
yield " <div class=\"divSliderModern\" style=\"margin: 0; padding: 0; width: 100%; overflow: hidden;\">
<div class=\"splide no-padding no-margin\" id=\"imageSlider\" style=\"width: 100%;\">
<div class=\"splide__track\" style=\"width: 100%;\">
<ul class=\"splide__list\" style=\"width: 100%;\">
<li class=\"splide__slide\" style=\"width: 100%;\">
<div class=\"slider-content-compact\">
<img src=\"";
// line 2109
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-compact\" alt=\"Produits souvenirs\" />
<div class=\"slider-text-compact\">
<span class=\"slider-title-compact\" style=\"color: #41a2aa\">Ajoutez vos favoris</span>
<span class=\"slider-subtitle-compact\" style=\"color: #f09e7a\">et créez vos souvenirs personnalisés !</span>
</div>
</div>
</li>
<li class=\"splide__slide\" style=\"width: 100%;\">
<div class=\"slider-content-compact\">
<img src=\"";
// line 2118
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-compact\" alt=\"Livre du séjour\" />
<div class=\"slider-text-compact\">
<span class=\"slider-title-compact\" style=\"color: #f09e7a\">Pensez à commander le livre</span>
<span class=\"slider-subtitle-compact\" style=\"color: #41a2aa\">et offrez le plus beau des cadeaux !</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
";
}
// line 2130
yield " </section>
";
}
// line 2132
yield " ";
// line 2133
yield "
<!-- Section de contenu à atteindre après le scroll -->
<div
class=\"no-margin\"
id=\"scrollTarget\"
style=\"width: 100%; background: #ffffff; padding-top: 16px\"
>
<div class=\"no-margin\" id=\"scrollTarget\" style=\"width: 100%\">
<!-- Header premium 5sur5 -->
<header class=\"premium-5sur5-day-header\" style=\"padding: 5px 25px; background: #ffffff; border-bottom: 1px solid rgba(65, 162, 170, 0.1); margin-bottom: -15px;\">
<div class=\"d-flex justify-content-between align-items-center flex-wrap gap-3\">
<div class=\"flex-grow-1\">
<h5 class=\"premium-5sur5-day-title\" style=\"margin: 0 0 6px 0; font-size: 1.3rem; font-weight: 700; color: #1a1a1a;\">
";
// line 2146
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.', 2146, $this->source); })()), "themSejour", [], "any", false, false, false, 2146), "html", null, true);
yield "
</h5>
<p class=\"premium-5sur5-day-subtitle\" style=\"margin: 0; font-size: 0.9rem; color: #6b7280; font-weight: 500;\">
du ";
// line 2149
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.', 2149, $this->source); })()), "dateDebutSejour", [], "any", false, false, false, 2149), "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 2150
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.', 2150, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 2150), "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 : <span style=\"color: #41a2aa; font-weight: 600;\">";
// line 2151
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.', 2151, $this->source); })()), "codeSejour", [], "any", false, false, false, 2151), "html", null, true);
yield "</span>
</p>
</div>
<!-- 🎯 MEDIA TOOLBAR PREMIUM - Style 5sur5 -->
<div class=\"premium-5sur5-actions-group\" style=\"gap: 8px;\">
<!-- Tout -->
<button class=\"premium-5sur5-action-btn mtb-btn ";
// line 2158
if ((((array_key_exists("attachementsCount", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 2158, $this->source); })()), 0)) : (0)) > 0)) {
yield "active";
}
yield "\" data-filter=\"all\" title=\"Tout\">
<i class=\"bi bi-grid-3x3-gap-fill\"></i>
<span>Tout</span>
<span class=\"premium-5sur5-badge\" style=\"background: rgba(65, 162, 170, 0.1); color: #41A2AA;\">";
// line 2161
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((array_key_exists("attachementsCount", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["attachementsCount"]) || array_key_exists("attachementsCount", $context) ? $context["attachementsCount"] : (function () { throw new RuntimeError('Variable "attachementsCount" does not exist.', 2161, $this->source); })()), 0)) : (0)), "html", null, true);
yield "</span>
</button>
<!-- Favoris -->
<button
class=\"premium-5sur5-action-btn mtb-btn ";
// line 2166
if ((((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 2166, $this->source); })()), 0)) : (0)) == 0)) {
yield "favoris-empty";
}
yield "\"
data-filter=\"favoris\"
title=\"";
// line 2168
if ((((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 2168, $this->source); })()), 0)) : (0)) == 0)) {
yield "Pas de favoris pour l'instant";
} else {
yield "Mes favoris";
}
yield "\"
";
// line 2169
if ((((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 2169, $this->source); })()), 0)) : (0)) == 0)) {
yield "disabled";
}
// line 2170
yield " >
<i class=\"bi bi-heart-fill\" style=\"color:";
// line 2171
if ((((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 2171, $this->source); })()), 0)) : (0)) == 0)) {
yield "#cbd5e1";
} else {
yield "#f56040";
}
yield ";\"></i>
<span>Favoris</span>
<span class=\"favoris-count\" id=\"mesFavCount\" data-bind=\"fav\" data-empty-text=\"Pas de favoris\">
";
// line 2174
if ((((array_key_exists("nblikes", $context)) ? (Twig\Extension\CoreExtension::default((isset($context["nblikes"]) || array_key_exists("nblikes", $context) ? $context["nblikes"] : (function () { throw new RuntimeError('Variable "nblikes" does not exist.', 2174, $this->source); })()), 0)) : (0)) == 0)) {
// line 2175
yield " <span class=\"favoris-empty-text\" style=\"color: #94a3b8; font-style: italic; font-size: 11px;\">Pas de favoris</span>
";
} else {
// line 2177
yield " <span class=\"premium-5sur5-badge\" style=\"background: rgba(245, 96, 64, 0.1); color: #F56040;\">";
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.', 2177, $this->source); })()), "html", null, true);
yield "</span>
";
}
// line 2179
yield "</span>
</button>
<!-- Vocal -->
<button class=\"premium-5sur5-action-btn mtb-btn\" data-filter=\"audio\" title=\"Messages vocaux\">
<span class=\"icon-telephone-voicemail\" style=\"display: inline-flex; align-items: center; gap: 0;\">
<i class=\"bi bi-telephone-fill\" style=\"color:#ffd700; font-size: 14px;\"></i>
<i class=\"bi bi-voicemail\" style=\"color:#ffd700; font-size: 14px; margin-left: -4px;\"></i>
</span>
<span>Vocal</span>
<span class=\"mtb-count premium-5sur5-badge\" data-bind=\"audio\" style=\"background: rgba(255, 215, 0, 0.15); color: #ffa500;\">0</span>
</button>
</div>
</div>
</header>
<!-- Navigation par jours - Style Premium compact -->
<div class=\"section-days\" style=\"background: #f8fafb; border-radius: 0px; padding: 10px 12px; margin: 12px 0 16px 0; border: 1px solid rgba(65, 162, 170, 0.1);\">
<div class=\"date-navigation d-flex align-items-center justify-content-center\" style=\"gap: 0;\">
<div class=\"date-container d-flex\" style=\"overflow-x: auto; overflow-y: hidden; gap: 10px; padding: 4px 0; scrollbar-width: thin; scrollbar-color: #41a2aa #f0f0f0;\">
";
// line 2199
$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.', 2199, $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 2200
yield " ";
$context["xDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate($context["x"]);
// line 2201
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.', 2201, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 2201));
// line 2202
yield " ";
$context["todayStr"] = $this->extensions['Twig\Extension\CoreExtension']->formatDate("now", "Y-m-d");
// line 2203
yield " ";
$context["currentDateStr"] = $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.', 2203, $this->source); })()), "Y-m-d");
// line 2204
yield "
";
// line 2205
if (((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 2205, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 2205, $this->source); })()))) {
// line 2206
yield " ";
// line 2207
yield " ";
$context["isPast"] = ((isset($context["currentDateStr"]) || array_key_exists("currentDateStr", $context) ? $context["currentDateStr"] : (function () { throw new RuntimeError('Variable "currentDateStr" does not exist.', 2207, $this->source); })()) < (isset($context["todayStr"]) || array_key_exists("todayStr", $context) ? $context["todayStr"] : (function () { throw new RuntimeError('Variable "todayStr" does not exist.', 2207, $this->source); })()));
// line 2208
yield " ";
$context["isCurrent"] = ((isset($context["currentDateStr"]) || array_key_exists("currentDateStr", $context) ? $context["currentDateStr"] : (function () { throw new RuntimeError('Variable "currentDateStr" does not exist.', 2208, $this->source); })()) == (isset($context["todayStr"]) || array_key_exists("todayStr", $context) ? $context["todayStr"] : (function () { throw new RuntimeError('Variable "todayStr" does not exist.', 2208, $this->source); })()));
// line 2209
yield " ";
$context["isFuture"] = ((isset($context["currentDateStr"]) || array_key_exists("currentDateStr", $context) ? $context["currentDateStr"] : (function () { throw new RuntimeError('Variable "currentDateStr" does not exist.', 2209, $this->source); })()) > (isset($context["todayStr"]) || array_key_exists("todayStr", $context) ? $context["todayStr"] : (function () { throw new RuntimeError('Variable "todayStr" does not exist.', 2209, $this->source); })()));
// line 2210
yield " ";
$context["isFirstDay"] = (CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "isFirstDay", [], "any", false, false, false, 2210) == "yes");
// line 2211
yield " ";
$context["isLastDay"] = (CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "isLastDay", [], "any", false, false, false, 2211) == "yes");
// line 2212
yield "
";
// line 2214
yield " ";
$context["cardClasses"] = ["date-card", "premium-day-card"];
// line 2215
yield " ";
if ((($tmp = (isset($context["isFuture"]) || array_key_exists("isFuture", $context) ? $context["isFuture"] : (function () { throw new RuntimeError('Variable "isFuture" does not exist.', 2215, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2216
yield " ";
$context["cardClasses"] = Twig\Extension\CoreExtension::merge((isset($context["cardClasses"]) || array_key_exists("cardClasses", $context) ? $context["cardClasses"] : (function () { throw new RuntimeError('Variable "cardClasses" does not exist.', 2216, $this->source); })()), ["premium-day-future", "notclickable"]);
// line 2217
yield " ";
} elseif ((($tmp = (isset($context["isCurrent"]) || array_key_exists("isCurrent", $context) ? $context["isCurrent"] : (function () { throw new RuntimeError('Variable "isCurrent" does not exist.', 2217, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2218
yield " ";
$context["cardClasses"] = Twig\Extension\CoreExtension::merge((isset($context["cardClasses"]) || array_key_exists("cardClasses", $context) ? $context["cardClasses"] : (function () { throw new RuntimeError('Variable "cardClasses" does not exist.', 2218, $this->source); })()), ["premium-day-current", "active"]);
// line 2219
yield " ";
} else {
// line 2220
yield " ";
$context["cardClasses"] = Twig\Extension\CoreExtension::merge((isset($context["cardClasses"]) || array_key_exists("cardClasses", $context) ? $context["cardClasses"] : (function () { throw new RuntimeError('Variable "cardClasses" does not exist.', 2220, $this->source); })()), ["premium-day-past"]);
// line 2221
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 2221) && !(isset($context["isCurrent"]) || array_key_exists("isCurrent", $context) ? $context["isCurrent"] : (function () { throw new RuntimeError('Variable "isCurrent" does not exist.', 2221, $this->source); })()))) {
// line 2222
yield " ";
$context["cardClasses"] = Twig\Extension\CoreExtension::merge((isset($context["cardClasses"]) || array_key_exists("cardClasses", $context) ? $context["cardClasses"] : (function () { throw new RuntimeError('Variable "cardClasses" does not exist.', 2222, $this->source); })()), ["active"]);
// line 2223
yield " ";
}
// line 2224
yield " ";
}
// line 2225
yield "
<div
class=\"";
// line 2227
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::join((isset($context["cardClasses"]) || array_key_exists("cardClasses", $context) ? $context["cardClasses"] : (function () { throw new RuntimeError('Variable "cardClasses" does not exist.', 2227, $this->source); })()), " "), "html", null, true);
yield " position-relative\"
id=\"iconedemoP";
// line 2228
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2228), "html", null, true);
yield "\"
";
// line 2229
if ((($tmp = (isset($context["isFuture"]) || array_key_exists("isFuture", $context) ? $context["isFuture"] : (function () { throw new RuntimeError('Variable "isFuture" does not exist.', 2229, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2230
yield " data-tooltip=\"Ce jour n'est pas encore accessible\"
aria-disabled=\"true\"
";
} else {
// line 2233
yield " data-bs-toggle=\"collapse\"
data-bs-target=\"#demP";
// line 2234
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2234), "html", null, true);
yield "\"
data-day-id=\"";
// line 2235
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["currentDateStr"]) || array_key_exists("currentDateStr", $context) ? $context["currentDateStr"] : (function () { throw new RuntimeError('Variable "currentDateStr" does not exist.', 2235, $this->source); })()), "html", null, true);
yield "\"
aria-expanded=\"false\"
aria-controls=\"demP";
// line 2237
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2237), "html", null, true);
yield "\"
tabindex=\"0\"
";
}
// line 2240
yield " >
";
// line 2242
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "isFirstDay", [], "any", false, false, false, 2242) == "yes")) {
// line 2243
yield " <span class=\"badge bg-success text-white position-absolute\" style=\"top: 5px; left: 5px; font-size: 0.5rem; padding: 0.2rem 0.2rem;\">
<i class=\"bi bi-play-fill me-1\"></i>
</span>
";
} elseif ((CoreExtension::getAttribute($this->env, $this->source, // line 2246
$context["groupAttach"], "isLastDay", [], "any", false, false, false, 2246) == "yes")) {
// line 2247
yield " <span class=\"badge bg-info text-white position-absolute\" style=\"top: 5px; left: 5px; font-size: 0.5rem; padding: 0.2rem 0.2rem;\">
<i class=\"bi bi-flag-fill ms-1\"></i>
</span>
";
}
// line 2251
yield "
<div class=\"card-content text-center\">
<span class=\"day\">
";
// line 2254
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.', 2254, $this->source); })()), "D"), ["Mon" => "lun.", "Tue" => "mar.", "Wed" => "mer.", "Thu" => "jeu.", "Fri" => "ven.", "Sat" => "sam.", "Sun" => "dim."]), "html", null, true);
// line 2258
yield "
</span>
<span class=\"full-date\">
";
// line 2261
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.', 2261, $this->source); })()), "d M Y"), ["Jan" => "janv.", "Feb" => "fév.", "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 2266
yield "
</span>
<ul class=\"media-list-horizontal\">
";
// line 2269
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 2269) > 0)) {
// line 2270
yield " <li><i class=\"bi bi-camera\"></i> ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 2270), "html", null, true);
yield "</li>
";
}
// line 2272
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2272) > 0)) {
// line 2273
yield " <li><i class=\"bi bi-mic-fill\"></i> ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2273), "html", null, true);
yield "</li>
";
}
// line 2275
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 2275) > 0)) {
// line 2276
yield " <li><i class=\"bi bi-camera-video\"></i> ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 2276), "html", null, true);
yield "</li>
";
}
// line 2278
yield " </ul>
</div>
</div>
";
}
// line 2282
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 2283
yield " </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 avec sidebar B2C -->
<div class=\"container-xxl\" style=\"padding: 0 25px; margin: 0 auto;\">
<div class=\"row g-2 col-md-12\" style=\"margin: 0;\">
<div class=\"col-lg-12 col-md-12\" style=\"padding: 0;\">
";
// line 2299
yield " <div class=\"container--gallery modern\" data-anchor=\"gallery\" style=\"padding: 0;\">
";
// line 2300
$context["lastValidIndex"] = 0;
// line 2301
yield " ";
$context["hasAttachments"] = false;
// line 2302
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.', 2302, $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 2303
yield " ";
$context["xDate"] = $this->extensions['Twig\Extension\CoreExtension']->convertDate($context["x"]);
// line 2304
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.', 2304, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 2304));
// line 2305
yield " ";
if (((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 2305, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 2305, $this->source); })()))) {
// line 2306
yield " ";
$context["lastValidIndex"] = CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2306);
// line 2307
yield " ";
$context["hasAttachments"] = true;
// line 2308
yield " <div
id=\"demP";
// line 2309
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2309), "html", null, true);
yield "\"
class=\"collapse ";
// line 2310
if ((CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 2310) && ((isset($context["xDate"]) || array_key_exists("xDate", $context) ? $context["xDate"] : (function () { throw new RuntimeError('Variable "xDate" does not exist.', 2310, $this->source); })()) <= (isset($context["finDate"]) || array_key_exists("finDate", $context) ? $context["finDate"] : (function () { throw new RuntimeError('Variable "finDate" does not exist.', 2310, $this->source); })())))) {
yield "show";
}
yield "\"
style=\"padding: 12px 0;\"
>
<div >
<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 2340
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 2348
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 2357
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 2357) > 0)) {
// line 2358
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 2359
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 2359), "html", null, true);
yield "
</span>
";
}
// line 2362
yield "
";
// line 2363
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 2363) > 0)) {
// line 2364
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 2365
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 2365), "html", null, true);
yield "
</span>
";
}
// line 2368
yield "
";
// line 2369
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2369) > 0)) {
// line 2370
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 2371
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2371), "html", null, true);
yield "
</span>
";
}
// line 2374
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 2392
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["loop"], "index", [], "any", false, false, false, 2392), "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 2405
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", true, true, false, 2405)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countPhotos", [], "any", false, false, false, 2405), 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 2410
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", true, true, false, 2410)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countVideos", [], "any", false, false, false, 2410), 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 2415
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", true, true, false, 2415)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2415), 0)) : (0)), "html", null, true);
yield "</span>
</div>
</div>
</div>
";
// line 2419
$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.', 2419, $this->source); })()), "jourdescripdate", [], "any", false, false, false, 2419));
foreach ($context['_seq'] as $context["_key"] => $context["description"]) {
// line 2420
yield " ";
if (($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, $context["description"], "datejourphoto", [], "any", false, false, false, 2420), "m/d/Y") != "")) {
if (($this->extensions['Twig\Extension\CoreExtension']->formatDate(CoreExtension::getAttribute($this->env, $this->source, // line 2421
$context["description"], "datejourphoto", [], "any", false, false, false, 2421), "m/d/Y") == $this->extensions['Twig\Extension\CoreExtension']->formatDate($context["x"], "m/d/Y"))) {
// line 2422
yield " <p class=\"description\" style=\"margin-left:2%;width:95%;margin-top:1%;margin-bottom:1%;text-align:left\">
";
// line 2424
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, 2424), "html", null, true));
yield "
</p> ";
}
// line 2426
yield "
";
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['description'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2428
yield "
<!-- 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 2441
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 2441));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 2442
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2442) == "photo")) {
// line 2443
yield "
<div class=\"column\" data-type=\"";
// line 2444
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2444), "html", null, true);
yield "\">
<div class=\"photo-zoom photo-item\">
<!-- Image sans lien -->
<img src=\"";
// line 2448
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2448), "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, 2448), "html", null, true);
yield "\" loading=\"lazy\" decoding=\"async\" />
<!-- Icône \"voir\" pour ouvrir le slider -->
<div class=\"view-icon\" onclick=\"viewImage('";
// line 2451
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 2451), "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 2458
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 2458), "html", null, true);
yield "\"
data-id=\"";
// line 2459
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "id_attchment", [], "any", false, false, false, 2459), "html", null, true);
yield "\"
data-sejour-id=\"";
// line 2460
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.', 2460, $this->source); })()), "id", [], "any", false, false, false, 2460), "html", null, true);
yield "\"
data-path=\"";
// line 2461
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2461), "html", null, true);
yield "\"
data-description=\"";
// line 2462
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2462), "html", null, true);
yield "\"
>
";
// line 2464
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.', 2464, $this->source); })()), "user", [], "any", false, false, false, 2464)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
yield " ";
if ((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "is_favorite", [], "any", false, false, false, 2464)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2465
yield " <i
class=\"bi bi-heart-fill\"
title=\"Sélectionnée\"
style=\"color: #f56040\"
></i>
";
} else {
// line 2471
yield " <i class=\"bi bi-heart\" title=\"Ajouter à ma sélection\"></i>
";
}
// line 2472
yield " ";
}
// line 2473
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 2487
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2487) != "")) {
// line 2488
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, 2488), "html", null, true);
yield "</h4>
";
}
// line 2490
yield " </div>
";
}
// line 2494
yield "
";
// line 2497
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2497) == "video")) {
// line 2498
yield "
<div class=\"column\" data-type=\"";
// line 2499
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2499), "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 2504
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2504), "html", null, true);
yield "\" type=\"video/mp4\" />
Votre navigateur ne supporte pas la lecture vidéo.
</video>
</div>
";
// line 2509
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2509) != "")) {
// line 2510
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, 2510), "html", null, true);
yield "</h4>
";
}
// line 2512
yield " </div>
";
}
// line 2513
yield "
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2516
yield " </div>
<!-- Section séparée pour les messages audio -->
";
// line 2519
if ((CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2519) > 0)) {
// line 2520
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 2524
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "countAudio", [], "any", false, false, false, 2524), "html", null, true);
yield ")
</h4>
";
// line 2527
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.', 2527, $this->source); })()), "codeSejour", [], "any", false, false, false, 2527), 0, 2) == "PF")) {
// line 2528
yield " ";
// line 2529
yield " <div class=\"audio-messages-container\" data-type=\"audio\" style=\"display: flex; flex-wrap: wrap; gap: 15px\">
";
// line 2530
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 2530));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 2531
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2531) == "message")) {
// line 2532
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 2536
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2536), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 2540
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2540) != "")) {
// line 2541
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 2543
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2543), "html", null, true);
yield "
</p>
</div>
";
}
// line 2547
yield " </div>
";
}
// line 2549
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2550
yield " </div>
";
} elseif ((((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, // line 2552
(isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 2552, $this->source); })()), "codeSejour", [], "any", false, false, false, 2552), 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.', 2552, $this->source); })()), "payment", [], "any", false, false, false, 2552) == 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.', 2552, $this->source); })()), "codeSejour", [], "any", false, false, false, 2552), 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.', 2552, $this->source); })()), "payment", [], "any", false, false, false, 2552) == 1)))) {
// line 2553
yield " ";
// line 2554
yield " <div class=\"audio-messages-container\" data-type=\"audio\" style=\"display: flex; flex-wrap: wrap; gap: 15px\">
";
// line 2555
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 2555));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 2556
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2556) == "message")) {
// line 2557
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 2561
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2561), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 2565
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2565) != "")) {
// line 2566
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 2568
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2568), "html", null, true);
yield "
</p>
</div>
";
}
// line 2572
yield " </div>
";
}
// line 2574
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2575
yield " </div>
";
} elseif ((((Twig\Extension\CoreExtension::slice($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, // line 2577
(isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 2577, $this->source); })()), "codeSejour", [], "any", false, false, false, 2577), 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.', 2577, $this->source); })()), "payment", [], "any", false, false, false, 2577) == 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.', 2577, $this->source); })()), "codeSejour", [], "any", false, false, false, 2577), 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.', 2577, $this->source); })()), "payment", [], "any", false, false, false, 2577) == 0)))) {
// line 2578
yield " ";
// line 2579
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 2584
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, $context["groupAttach"], "attachments", [], "any", false, false, false, 2584));
foreach ($context['_seq'] as $context["_key"] => $context["attach"]) {
// line 2585
yield " ";
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "libiller", [], "any", false, false, false, 2585) == "message")) {
// line 2586
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 2590
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "path", [], "any", false, false, false, 2590), "html", null, true);
yield "\" type=\"audio/mp3\" />
Votre navigateur ne supporte pas la lecture audio.
</audio>
</div>
";
// line 2594
if ((CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2594) != "")) {
// line 2595
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 2597
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["attach"], "descreption", [], "any", false, false, false, 2597), "html", null, true);
yield "
</p>
</div>
";
}
// line 2601
yield " </div>
";
}
// line 2603
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['attach'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2604
yield " </div>
";
}
// line 2606
yield " </div>
";
}
// line 2608
yield " </div>
</div>
</div>
";
}
// line 2612
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 2613
yield " ";
if ((($tmp = !(isset($context["hasAttachments"]) || array_key_exists("hasAttachments", $context) ? $context["hasAttachments"] : (function () { throw new RuntimeError('Variable "hasAttachments" does not exist.', 2613, $this->source); })())) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 2614
yield " <section class=\"stay-empty-wrapper\">
<div class=\"stay-empty-card\">
<h2 class=\"stay-empty-title\">Aucune photo pour le moment, c'est normal.</h2>
<p class=\"stay-empty-intro\">
Le séjour démarre et l'équipe est pleinement avec les enfants : installation, découverte des lieux, premiers temps de vie de groupe.<br>
Les premières photos ou messages vocaux sont généralement publiés en fin de journée d'arrivée ou le lendemain matin.
</p>
<!-- Bloc \"Ce qui va arriver\" + illustration -->
<div class=\"stay-empty-expect\">
<div class=\"stay-empty-expect-content\">
<p class=\"stay-empty-expect-label\">Ce qui va arriver dans les prochaines heures :</p>
<ul class=\"stay-empty-list\">
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--check\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<polyline points=\"20 6 9 17 4 12\"/>
</svg>
</span>
<span>1 photo ou message « Bien arrivés »</span>
</li>
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--photos\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>
<circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/>
<polyline points=\"21 15 16 10 5 21\"/>
</svg>
</span>
<span>Quelques photos du lieu et du groupe</span>
</li>
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--vocal\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/>
<path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>
</svg>
</span>
<span>Un message vocal dès que le programme le permet</span>
</li>
</ul>
</div>
<!-- Illustration compacte -->
<div class=\"stay-empty-illustration\">
<svg class=\"stay-empty-phone\" viewBox=\"0 0 120 200\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">
<rect x=\"0\" y=\"0\" width=\"120\" height=\"200\" rx=\"18\" fill=\"#1A1A2E\"/>
<rect x=\"6\" y=\"10\" width=\"108\" height=\"180\" rx=\"14\" fill=\"#fff\"/>
<rect x=\"12\" y=\"22\" width=\"44\" height=\"44\" rx=\"8\" fill=\"#F09E7A\" opacity=\"0.2\"/>
<rect x=\"62\" y=\"22\" width=\"46\" height=\"44\" rx=\"8\" fill=\"#41A2AA\" opacity=\"0.2\"/>
<rect x=\"12\" y=\"72\" width=\"96\" height=\"56\" rx=\"8\" fill=\"#9B8BF4\" opacity=\"0.15\"/>
<circle cx=\"34\" cy=\"44\" r=\"10\" fill=\"#F09E7A\"/>
<path d=\"M29 47l4-5 3 3 4-5 5 7H29z\" fill=\"#fff\"/>
<circle cx=\"85\" cy=\"44\" r=\"10\" fill=\"#41A2AA\"/>
<path d=\"M82 41v6M85 38v9M88 42v4\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"/>
<circle cx=\"60\" cy=\"100\" r=\"12\" fill=\"rgba(255,255,255,0.9)\"/>
<path d=\"M56 94l10 6-10 6V94z\" fill=\"#9B8BF4\"/>
<rect x=\"12\" y=\"138\" width=\"96\" height=\"44\" rx=\"8\" fill=\"#F8FAFB\"/>
<circle cx=\"34\" cy=\"160\" r=\"8\" fill=\"#E5E9F0\"/>
<circle cx=\"60\" cy=\"160\" r=\"8\" fill=\"#41A2AA\"/>
<circle cx=\"86\" cy=\"160\" r=\"8\" fill=\"#E5E9F0\"/>
<rect x=\"42\" y=\"4\" width=\"36\" height=\"5\" rx=\"2.5\" fill=\"#1A1A2E\"/>
</svg>
<div class=\"stay-empty-bubble stay-empty-bubble--arrived\">
<span class=\"stay-empty-bubble-icon\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<polyline points=\"20 6 9 17 4 12\"/>
</svg>
</span>
Bien arrivés !
</div>
<div class=\"stay-empty-bubble stay-empty-bubble--photos\">
<span class=\"stay-empty-bubble-icon\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>
<circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/>
<polyline points=\"21 15 16 10 5 21\"/>
</svg>
</span>
+12 photos
</div>
<span class=\"stay-empty-illustration-label\">Exemple de nouvelles à venir</span>
</div>
</div>
<!-- Note rassurante -->
<div class=\"stay-empty-reassurance\">
<svg class=\"stay-empty-reassurance-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/>
</svg>
<p>En cas de besoin important, l'école ou l'organisateur vous contactera directement, comme d'habitude.</p>
</div>
<!-- CTA -->
<div class=\"stay-empty-footer\">
<a href=\"";
// line 2714
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ParametresParent");
yield "\" class=\"stay-empty-cta\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>
<path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>
</svg>
Gérer mes notifications
</a>
</div>
</div>
</section>
<div style=\"height: 200px;\"></div>
";
}
// line 2727
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>
<!-- CTA discret et premium pour rediriger vers la boutique -->
<a href=\"";
// line 2837
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("boutique5sur5");
yield "\" class=\"premium-cta-subtle\" style=\"margin: 20px 0; padding: 16px 20px; background: rgba(65, 162, 170, 0.04); border: 1px solid rgba(65, 162, 170, 0.15); border-radius: 12px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: space-between; gap: 12px; text-decoration: none;\">
<div style=\"display: flex; align-items: center; gap: 12px; flex: 1;\">
<div style=\"width: 40px; height: 40px; background: rgba(65, 162, 170, 0.1); border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;\">
<i class=\"bi bi-gift\" style=\"font-size: 18px; color: #41A2AA;\"></i>
</div>
<div style=\"flex: 1; min-width: 0;\">
<p style=\"margin: 0; font-size: 14px; font-weight: 600; color: #1a1a1a; line-height: 1.4;\">Créer des souvenirs avec vos \${favoriteCount} photos</p>
<p style=\"margin: 4px 0 0 0; font-size: 12px; color: #6b7280; line-height: 1.4;\">Albums, tirages et livres personnalisés</p>
</div>
</div>
<i class=\"bi bi-chevron-right\" style=\"font-size: 18px; color: #41A2AA; flex-shrink: 0;\"></i>
</a>
</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');
// ✅ IMPORTANT : Le bouton favoris reste TOUJOURS accessible, peu importe la vue
switch (currentView) {
case 'favorites':
// Dans les favoris : bouton actif marqué visuellement
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 reste actif et accessible ✅
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;
case 'days':
default:
// Vue normale : bouton normal et accessible
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 photo-item\" data-id=\"\${item.id}\" style=\"position: relative; width: 100%; aspect-ratio: 1; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); background: #f0f0f0;\">
\${item.type === 'image' ?
`<img src=\"\${item.url}\" alt=\"Photo favorite\" loading=\"lazy\" style=\"width: 100%; height: 100%; object-fit: cover; display: block;\">` :
item.type === 'video' ?
`<video src=\"\${item.url}\" poster=\"\${item.thumbnail}\" style=\"width: 100%; height: 100%; object-fit: cover; display: block;\"></video>
<div class=\"video-overlay\" style=\"position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.3);\"><i class=\"bi bi-play-circle\" style=\"font-size: 48px; color: white;\"></i></div>` :
`<div class=\"audio-item\" style=\"width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, rgba(65, 162, 170, 0.1) 0%, rgba(245, 96, 64, 0.1) 100%);\">
<i class=\"bi bi-mic-fill\" style=\"font-size: 32px; color: #41A2AA; margin-bottom: 8px;\"></i>
<span style=\"font-size: 14px; color: #6b7280; font-weight: 600;\">Audio \${item.duration || '00:00'}</span>
</div>`
}
<!-- Actions overlay -->
<div class=\"favorite-actions\" style=\"position: absolute; top: 8px; right: 8px; display: flex; gap: 6px; opacity: 0; transition: opacity 0.3s ease;\">
<button class=\"btn-view-favorite\" onclick=\"viewFavorite('\${item.id}')\" title=\"Voir en grand\" style=\"width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.9); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">
<i class=\"bi bi-eye\" style=\"font-size: 14px; color: #41A2AA;\"></i>
</button>
<button class=\"btn-remove-favorite\" onclick=\"removeFavorite('\${item.id}')\" title=\"Retirer des favoris\" style=\"width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.9); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">
<i class=\"bi bi-heart-fill\" style=\"font-size: 14px; color: #f56040;\"></i>
</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)
// 🗑️ SUPPRIMÉ : generateProductSuggestions() - remplacé par le sidebar e-commerce
// Le sidebar e-commerce (sidebar-ecommerce-pro.js) gère maintenant tous les produits
// de manière centralisée et dynamique
// Fonction pour obtenir le nombre actuel de favoris
function getCurrentFavoriteCount() {
return getFavoriteItems().length;
}
// 🎯 SUPPRIMÉ: Fonction dupliquée - la vraie est dans parent-toasts.js
// Cette fonction était en conflit avec updateProductSuggestions() de parent-toasts.js
// qui gère déjà les mises à jour dynamiques avec dates et contexte
// Fonction legacy pour compatibilité (redirige vers parent-toasts.js)
window.updateProductSuggestionsLive = function(favoriteCount) {
console.log('[Legacy] updateProductSuggestionsLive appelé, redirection vers parent-toasts.js');
// La vraie logique est dans parent-toasts.js > updateProductSuggestions()
// qui est appelée automatiquement par le MutationObserver
}
// 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 5176
if (((isset($context["lastValidIndex"]) || array_key_exists("lastValidIndex", $context) ? $context["lastValidIndex"] : (function () { throw new RuntimeError('Variable "lastValidIndex" does not exist.', 5176, $this->source); })()) > 0)) {
// line 5177
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 5182
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.', 5182, $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 5190
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.', 5190, $this->source); })()), "html", null, true);
yield ") {
dateCards.forEach(card => {
if (card && card.classList) {
card.classList.remove('active');
}
});
const targetCard = dateCards[";
// line 5196
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.', 5196, $this->source); })()) - 1), "html", null, true);
yield "];
if (targetCard && targetCard.classList) {
targetCard.classList.add('active');
}
}
}
}
});
</script>
";
}
// line 5206
yield " </div>
</div>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 5209
/**
* @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 5210
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 mesFavCount = document.getElementById('mesFavCount');
const count = mesFavCount ? getFavorisCount() : 0;
counter.textContent = count;
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 5458
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 5467
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 5525
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 5658
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 5711
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 5733
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.', 5733, $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;
// 🎯 CAS SPÉCIAL : Bouton favoris (filtre + ouvre sidebar, JAMAIS de modification du compteur)
if (filter === 'favoris') {
console.log('💗 BOUTON FAVORIS CLIQUÉ - Vue favoris + ouverture sidebar');
// 🛡️ Protection ABSOLUE : empêcher TOUTE propagation vers d'autres handlers
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
// 1️⃣ Activer visuellement le bouton favoris
const mtbBtns = document.querySelectorAll('.mtb-btn[data-filter]');
mtbBtns.forEach(b => {
if (b === btn) {
b.classList.add('active');
} else {
b.classList.remove('active');
}
});
// 2️⃣ Filtrer la galerie pour afficher uniquement les favoris
if (typeof window.filterContent === 'function') {
window.filterContent('favoris');
} else {
console.error('❌ window.filterContent non disponible');
}
// 3️⃣ Ouvrir le sidebar e-commerce via le bouton cadeau
const giftButton = document.getElementById('gift-button-trigger');
if (giftButton) {
console.log('🎁 Ouverture du sidebar via bouton cadeau');
setTimeout(() => giftButton.click(), 100); // Petit délai pour éviter les conflits
} else {
console.warn('⚠️ Bouton cadeau non trouvé');
}
return; // ⛔ SORTIE IMMÉDIATE - pas de traitement supplémentaire
}
// 🎯 CAS SPÉCIAL : Bouton audio avec 0 message → afficher popover
if (filter === 'audio') {
const audioCount = btn.querySelector('.mtb-count[data-bind=\"audio\"]');
const count = audioCount ? parseInt(audioCount.textContent.trim()) : 0;
if (count === 0) {
console.log('🎤 Bouton audio cliqué avec 0 message - affichage popover');
e.stopPropagation();
e.preventDefault();
// Afficher le popover premium
showAudioEmptyPopover(btn);
return; // ⛔ Ne pas filtrer si 0 message
}
}
// 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);
};
// 🛡️ PROTECTION ABSOLUE : Empêcher TOUTE interaction directe avec le compteur favoris
document.addEventListener('DOMContentLoaded', () => {
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
// Bloquer tous les clics directs sur le compteur
mesFavCount.addEventListener('click', (e) => {
console.log('⚠️ Clic sur mesFavCount bloqué');
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
return false;
}, true); // Capture phase pour intercepter avant tout autre listener
// Empêcher le double-clic
mesFavCount.addEventListener('dblclick', (e) => {
console.log('⚠️ Double-clic sur mesFavCount bloqué');
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
return false;
}, true);
// Ajouter un style pour indiquer visuellement que c'est non-cliquable
mesFavCount.style.pointerEvents = 'none';
mesFavCount.style.userSelect = 'none';
console.log('🛡️ Protection mesFavCount activée - compteur non-cliquable');
}
// 🎤 Initialiser l'état du bouton audio (griser si 0 message)
initAudioButtonState();
});
// 🎤 Fonction pour afficher le popover \"Pas de message audio\"
function showAudioEmptyPopover(btn) {
// Supprimer les popovers existants
const existingPopover = document.querySelector('.audio-empty-popover');
if (existingPopover) {
existingPopover.remove();
}
// Créer le popover
const popover = document.createElement('div');
popover.className = 'audio-empty-popover';
popover.innerHTML = `
<div class=\"audio-popover-content\">
<div class=\"audio-popover-icon\">
<i class=\"bi bi-mic-mute-fill\"></i>
</div>
<div class=\"audio-popover-text\">
<strong>Aucun message audio</strong>
<p>Pas de message audio soumis pour l'instant</p>
</div>
</div>
`;
// Positionner le popover
document.body.appendChild(popover);
const btnRect = btn.getBoundingClientRect();
popover.style.position = 'fixed';
popover.style.top = `\${btnRect.bottom + 10}px`;
popover.style.left = `\${btnRect.left + (btnRect.width / 2)}px`;
popover.style.transform = 'translateX(-50%)';
// Animation d'entrée
setTimeout(() => popover.classList.add('show'), 10);
// Auto-fermeture après 3 secondes
setTimeout(() => {
popover.classList.remove('show');
setTimeout(() => popover.remove(), 300);
}, 3000);
// Fermer au clic ailleurs
const closeOnClick = (e) => {
if (!popover.contains(e.target) && !btn.contains(e.target)) {
popover.classList.remove('show');
setTimeout(() => popover.remove(), 300);
document.removeEventListener('click', closeOnClick);
}
};
setTimeout(() => document.addEventListener('click', closeOnClick), 100);
}
// 🎤 Fonction pour initialiser l'état du bouton audio
function initAudioButtonState() {
const audioBtn = document.querySelector('.mtb-btn[data-filter=\"audio\"]');
if (!audioBtn) return;
const audioCount = audioBtn.querySelector('.mtb-count[data-bind=\"audio\"]');
if (!audioCount) return;
const count = parseInt(audioCount.textContent.trim()) || 0;
if (count === 0) {
// Masquer le compteur \"0\"
audioCount.style.display = 'none';
// Griser l'icône
audioBtn.classList.add('disabled-audio');
audioBtn.style.opacity = '0.4';
audioBtn.style.cursor = 'not-allowed';
const icons = audioBtn.querySelectorAll('i');
icons.forEach(icon => {
icon.style.color = '#999 !important';
icon.style.filter = 'grayscale(100%)';
});
console.log('🎤 Bouton audio désactivé (0 message)');
} else {
// Afficher le compteur
audioCount.style.display = 'inline';
// Activer l'icône
audioBtn.classList.remove('disabled-audio');
audioBtn.style.opacity = '1';
audioBtn.style.cursor = 'pointer';
console.log(`🎤 Bouton audio activé (\${count} message\${count > 1 ? 's' : ''})`);
}
}
// 🎤 Observer les changements du compteur audio pour mettre à jour l'état
const audioCountObserver = new MutationObserver(() => {
initAudioButtonState();
});
// Démarrer l'observation quand le DOM est prêt
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
const audioCount = document.querySelector('.mtb-count[data-bind=\"audio\"]');
if (audioCount) {
audioCountObserver.observe(audioCount, {
childList: true,
characterData: true,
subtree: true
});
}
});
} else {
const audioCount = document.querySelector('.mtb-count[data-bind=\"audio\"]');
if (audioCount) {
audioCountObserver.observe(audioCount, {
childList: true,
characterData: true,
subtree: true
});
}
}
// 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 6179
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 6187
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 6254
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 6899
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.', 6899, $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 6914
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 6922
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 6930
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();
}
// Fonction helper pour lire le compteur de favoris (gère les badges premium)
function getFavorisCount() {
const mesFavCount = document.getElementById('mesFavCount');
if (!mesFavCount) return 0;
const badge = mesFavCount.querySelector('.premium-5sur5-badge');
if (badge) {
return parseInt(badge.textContent.trim() || '0');
}
const countText = mesFavCount.textContent.trim();
if (countText === 'Pas de favoris' || countText.includes('Pas de favoris')) {
return 0;
}
return parseInt(countText || '0');
}
// Fonction helper pour mettre à jour l'affichage du compteur de favoris
function updateFavorisDisplay(count) {
const mesFavCount = document.getElementById('mesFavCount');
const favorisBtn = document.querySelector('.mtb-btn[data-filter=\"favoris\"]');
const favorisIcon = favorisBtn?.querySelector('i.bi-heart-fill');
if (!mesFavCount || !favorisBtn) return;
if (count === 0) {
// Afficher \"Pas de favoris\" au lieu de \"0\" avec style premium
mesFavCount.innerHTML = '<span class=\"favoris-empty-text\" style=\"color: #94a3b8; font-style: italic; font-size: 11px;\">Pas de favoris</span>';
favorisBtn.classList.add('favoris-empty');
favorisBtn.disabled = true;
favorisBtn.title = \"Pas de favoris pour l'instant\";
if (favorisIcon) {
favorisIcon.style.color = '#cbd5e1';
}
} else {
// Afficher le badge premium avec le nombre
mesFavCount.innerHTML = `<span class=\"premium-5sur5-badge\" style=\"background: rgba(245, 96, 64, 0.1); color: #F56040; font-size: 10px; padding: 2px 6px;\">\${count}</span>`;
favorisBtn.classList.remove('favoris-empty');
favorisBtn.disabled = false;
favorisBtn.title = \"Mes favoris\";
if (favorisIcon) {
favorisIcon.style.color = '#f56040';
}
}
}
function supprimerFavoris(\$id, \$idSejour) {
// 🚨 LOG CRITIQUE : Tracer TOUS les appels à supprimerFavoris
console.error('🚨🚨🚨 supprimerFavoris() APPELÉ ! 🚨🚨🚨', {
id: \$id,
idSejour: \$idSejour,
stack: new Error().stack,
mesFavCountAvant: document.getElementById('mesFavCount')?.textContent
});
// 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 UNIQUEMENT #mesFavCount (source unique de vérité)
// L'observer se chargera de synchroniser giftCount automatiquement
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
let currentCount = getFavorisCount();
currentCount = Math.max(0, currentCount - 1); // Empêche d'aller en dessous de 0
updateFavorisDisplay(currentCount);
console.log('[supprimerFavoris] ✅ mesFavCount décrémenté:', currentCount);
// L'observer MutationObserver va automatiquement :
// - Synchroniser giftCount
// - Mettre à jour le sidebar
// - Mettre à jour product-suggestions
}
// 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 7085
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) {
// 🟢 LOG : Tracer les ajouts de favoris
console.log('🟢 AddFavoris() APPELÉ', {
id: \$id,
idSejour: \$idSejour,
mesFavCountAvant: document.getElementById('mesFavCount')?.textContent
});
// 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>\");
// METTRE À JOUR UNIQUEMENT #mesFavCount (source unique de vérité)
// L'observer se chargera de synchroniser giftCount automatiquement
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
let currentCount = getFavorisCount();
currentCount++;
updateFavorisDisplay(currentCount);
console.log('[AddFavoris] ✅ mesFavCount incrémenté:', currentCount);
// L'observer MutationObserver va automatiquement :
// - Synchroniser giftCount
// - Mettre à jour le sidebar
// - Mettre à jour product-suggestions
}
// 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 7152
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 (e) {
const heartIcon = \$(this);
// 🛡️ PROTECTION : Ignorer les clics sur le bouton favoris de la toolbar
// Ce bouton sert uniquement à FILTRER, pas à ajouter/supprimer des favoris
if (heartIcon.closest('.mtb-btn').length > 0) {
console.log('💗 Clic sur bouton toolbar favoris ignoré (gestion séparée)');
return; // Ne rien faire, c'est géré par le listener de la toolbar
}
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();
// 🎯 Product suggestions sont mis à jour automatiquement par le MutationObserver
// dans parent-toasts.js qui observe #mesFavCount
// updateProductSuggestionsLive(currentCount); // ← SUPPRIMÉ (doublon)
}
}, 50);
});
// Ajoutez les événements sur les icônes de cœur
document.querySelectorAll('.IconDelete').forEach((icon) => {
icon.addEventListener('click', (event) => {
const isFavorite = icon && icon.classList && icon.classList.contains('bi-heart-fill');
if (isFavorite) {
removeFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart-fill');
icon.classList.add('bi-heart');
}
} else {
addFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart');
icon.classList.add('bi-heart-fill');
}
}
});
});
// Vérifie l'état initial
checkFavoritesAlert();
// ⚡ OPTIMISÉ: Réduction du délai d'initialisation
</script>
<!-- Initialisation -->
<script>
// ⚡ OPTIMISATION: Différer l'initialisation d'AOS pour ne pas bloquer le chargement
setTimeout(function() {
AOS.init({
duration: 800,
easing: \"ease-in-out\"
});
}, 100);
// 🎯 DAY FILTER DROPDOWN LOGIC (Senior UX)
document.addEventListener('DOMContentLoaded', function() {
initializeDayFilters();
// Initialiser le compteur audio avec la valeur correcte
setTimeout(function() {
updateAudioButtonState('days');
}, 500);
});
function initializeDayFilters() {
const dropdowns = document.querySelectorAll('.day-filter-dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('.filter-toggle');
const menu = dropdown.querySelector('.filter-dropdown-menu');
const options = dropdown.querySelectorAll('.filter-option');
const dayIndex = dropdown.dataset.dayIndex;
const dayContainer = document.getElementById(`demP\${dayIndex}`);
if (!toggle || !menu || !dayContainer) return;
// Calculer et mettre à jour les compteurs initiaux
updateDayFilterCounts(dropdown, dayContainer);
// Toggle dropdown
toggle.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = dropdown.classList.contains('open');
// Fermer tous les autres dropdowns
document.querySelectorAll('.day-filter-dropdown.open').forEach(d => {
if (d !== dropdown) d.classList.remove('open');
});
// Toggle le dropdown actuel
dropdown.classList.toggle('open', !isOpen);
});
// Gérer les clics sur les options
options.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const filter = option.dataset.filter;
// Mettre à jour l'état actif
options.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
// Appliquer le filtre
applyDayFilter(dayContainer, filter);
// Fermer le dropdown
dropdown.classList.remove('open');
});
});
});
// Fermer les dropdowns en cliquant ailleurs
document.addEventListener('click', () => {
document.querySelectorAll('.day-filter-dropdown.open').forEach(dropdown => {
dropdown.classList.remove('open');
});
});
}
function updateDayFilterCounts(dropdown, dayContainer) {
const photoItems = dayContainer.querySelectorAll('[data-type=\"photo\"]');
const videoItems = dayContainer.querySelectorAll('[data-type=\"video\"]');
// Compter les messages audio individuels plutôt que les conteneurs
const audioMessageItems = dayContainer.querySelectorAll('.audio-message-item[data-type=\"audio\"]');
const audioContainers = dayContainer.querySelectorAll('.audio-messages-container[data-type=\"audio\"]');
const audioRestricted = dayContainer.querySelectorAll('.audio-messages-restricted[data-type=\"audio\"]');
const allItems = dayContainer.querySelectorAll('[data-type]');
const countAll = dropdown.querySelector('[data-count-all]');
const countPhoto = dropdown.querySelector('[data-count-photo]');
const countVideo = dropdown.querySelector('[data-count-video]');
const countAudio = dropdown.querySelector('[data-count-audio]');
// Compter les messages audio individuels + conteneurs + sections restreintes
const totalAudio = audioMessageItems.length + audioContainers.length + audioRestricted.length;
if (countAll) countAll.textContent = allItems.length;
if (countPhoto) countPhoto.textContent = photoItems.length;
if (countVideo) countVideo.textContent = videoItems.length;
if (countAudio) countAudio.textContent = totalAudio;
// Masquer les options sans contenu
const photoOption = dropdown.querySelector('[data-filter=\"photo\"]');
const videoOption = dropdown.querySelector('[data-filter=\"video\"]');
const audioOption = dropdown.querySelector('[data-filter=\"audio\"]');
if (photoOption) photoOption.style.display = photoItems.length > 0 ? 'flex' : 'none';
if (videoOption) videoOption.style.display = videoItems.length > 0 ? 'flex' : 'none';
if (audioOption) audioOption.style.display = totalAudio > 0 ? 'flex' : 'none';
}
function applyDayFilter(dayContainer, filter) {
const items = dayContainer.querySelectorAll('[data-type]');
items.forEach(item => {
if (filter === 'all' || item.dataset.type === filter) {
item.style.display = '';
item.classList.remove('filtered-out');
} else {
item.style.display = 'none';
item.classList.add('filtered-out');
}
});
// Animation fluide pour les éléments visibles
requestAnimationFrame(() => {
const visibleItems = dayContainer.querySelectorAll('[data-type]:not(.filtered-out)');
visibleItems.forEach((item, index) => {
item.style.animation = `fadeInUp 0.3s ease forwards \${index * 0.05}s`;
});
});
}
// Animation CSS pour fadeInUp
if (!document.querySelector('#dayFilterAnimations')) {
const style = document.createElement('style');
style.id = 'dayFilterAnimations';
style.textContent = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
document.head.appendChild(style);
}
document.addEventListener(\"DOMContentLoaded\", function () {
const dateCards = document.querySelectorAll(\".date-card\");
const sections = document.querySelectorAll(\".collapse\");
dateCards.forEach((card) => {
card.addEventListener(\"click\", function () {
// Supprimer les classes actives des autres cartes et sections
dateCards.forEach((c) => c.classList.remove(\"active\"));
sections.forEach((s) => s.classList.remove(\"show\"));
// Ajouter la classe active à la carte cliquée
this.classList.add(\"active\");
// Récupérer la cible et afficher la bonne section
const targetId = this.getAttribute(\"data-bs-target\");
const targetSection = document.querySelector(targetId);
if (targetSection) {
targetSection.classList.add(\"show\");
}
});
});
});
document.addEventListener(\"DOMContentLoaded\", function () {
// Initialisation du carrousel Splide
var splide = new Splide(\"#imageSlider\", {
type: \"loop\",
perPage: 1,
autoplay: true,
interval: 6000,
pauseOnHover: false,
pauseOnFocus: false,
pagination: false,
arrows: false,
});
splide.mount();
// ⚠️ Scroll automatique désactivé pour meilleure UX
// Les utilisateurs peuvent défiler manuellement quand ils le souhaitent
});
</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 7533
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.', 7533, $this->source); })()), "session", [], "any", false, false, false, 7533), "get", ["paymentmoniteco"], "method", false, false, false, 7533)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) {
// line 7534
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.', 7534, $this->source); })()), "session", [], "any", false, false, false, 7534), "get", ["paymentmoniteco"], "method", false, false, false, 7534) == "succses")) {
// line 7535
yield "
Swal.fire({
icon: 'success',
title: ' succès ',
text: 'votre commande est validée'
});
";
}
// line 7545
yield " ";
}
// line 7546
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 7620
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.', 7620, $this->source); })()), "user", [], "any", false, false, false, 7620), "showpubprod", [], "any", false, false, false, 7620) != "false")) {
// line 7621
yield " \$('#btnPubProd').click();
\$('.modal-backdrop').css('background-color', 'rgba(0, 0, 0, 0.2)');
";
}
// line 7624
yield " });
\$(\"#closeImage\").click(function () {
\$('#myModalImage').css('display', \"none\");
});
\$.ajax({
type: \"POST\",
url: \"";
// line 7630
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;
}
console.log('🎁 [OPEN SIDEBAR] Ouverture du sidebar e-commerce...');
const favCount = window.getFavoriteCount ? window.getFavoriteCount() : 0;
console.log('🎁 [OPEN SIDEBAR] Favoris actuels:', favCount);
// D'abord ouvrir le sidebar pour que les éléments soient dans le DOM
sidebar.classList.add('active');
console.log('🎁 [OPEN SIDEBAR] Sidebar classe \"active\" ajoutée');
// Puis mettre à jour le contenu (petit délai pour que le DOM soit prêt)
setTimeout(() => {
console.log('🎁 [OPEN SIDEBAR] Mise à jour du contenu...');
// Appeler la fonction de parent-toasts.js
if (typeof window.parentConversion !== 'undefined' && typeof window.parentConversion.updateSidebar === 'function') {
console.log('✅ [OPEN SIDEBAR] Appel de parentConversion.updateSidebar()');
window.parentConversion.updateSidebar();
} else {
console.warn('⚠️ [OPEN SIDEBAR] parent-toasts.js non disponible, fallback sur fonction locale');
// Fallback sur la fonction locale
if (typeof window.updateEcommerceSidebarContent === 'function') {
window.updateEcommerceSidebarContent(favCount);
}
}
}, 100); // 100ms de délai pour que le sidebar soit visible
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 LEGACY SUPPRIMÉE - Utiliser parent-toasts.js à la place
// Cette fonction était en conflit avec updateEcommerceSidebar() de parent-toasts.js
// qui gère TOUT : titre, sous-titre, compteurs, visibilité des produits, CTA, etc.
window.updateEcommerceSidebarContent = function updateEcommerceSidebarContent(favoriteCount) {
console.warn('[LEGACY] updateEcommerceSidebarContent appelé - Redirection vers parent-toasts.js');
// Rediriger vers la vraie fonction
if (typeof window.parentConversion !== 'undefined' && typeof window.parentConversion.updateSidebar === 'function') {
window.parentConversion.updateSidebar();
} else {
console.error('[LEGACY] parent-toasts.js non disponible !');
}
}
// 🚫 FONCTION SUPPRIMÉE - Géré par parent-toasts.js
// Tout est maintenant géré dans updateEcommerceSidebar() de parent-toasts.js
// 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 8079
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("EditionAlbum");
yield "?favorites=\${favoriteCount}`;
break;
case 'digital':
orderUrl = `";
// line 8082
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 15]);
yield "?favorites=\${favoriteCount}`;
break;
case 'prints':
orderUrl = `";
// line 8085
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 8124
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("EditionAlbum");
yield "?favorites=\${favoriteCount}`;
break;
case 'digital':
testUrl = `";
// line 8127
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("PackPhotosNumerique_Favoris", ["nbr" => 15]);
yield "?favorites=\${favoriteCount}`;
break;
case 'prints':
testUrl = `";
// line 8130
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();
};
// Fonction centrale de mise à jour de TOUS les compteurs de favoris
window.updateAllFavoriteCounters = function() {
// Récupérer le compte depuis #mesFavCount (source unique de vérité)
const mesFavCount = document.getElementById('mesFavCount');
let favoriteCount = 0;
if (mesFavCount) {
favoriteCount = parseInt(mesFavCount.textContent || '0');
}
console.log('[updateAllFavoriteCounters] 💗 Synchronisation:', favoriteCount, 'favoris');
// Synchroniser giftCount (bouton cadeau) avec animation
const giftCount = document.getElementById('giftCount');
if (giftCount) {
giftCount.textContent = favoriteCount;
giftCount.classList.remove('gift-count-bounce');
setTimeout(() => giftCount.classList.add('gift-count-bounce'), 10);
}
// 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 8323
if (((isset($context["sejour"]) || array_key_exists("sejour", $context) ? $context["sejour"] : (function () { throw new RuntimeError('Variable "sejour" does not exist.', 8323, $this->source); })()) && CoreExtension::getAttribute($this->env, $this->source, ($context["sejour"] ?? null), "dateFinSejour", [], "any", true, true, false, 8323))) {
// line 8324
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.', 8324, $this->source); })()), "dateFinSejour", [], "any", false, false, false, 8324), "Y-m-d"), "html", null, true);
yield "');
";
}
// line 8326
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>
";
// line 8391
yield "<aside class=\"col-lg-3 d-none d-lg-block\" style=\"position:sticky;top:100px;\">
<div id=\"parentSuggestions\" class=\"pcb-sticky\">
";
// line 8394
yield " </div>
</aside>
";
// line 8398
yield "<div class=\"col-12 d-lg-none mt-3\">
<div id=\"parentSuggestionsMobile\">
";
// line 8401
yield " </div>
</div>
";
// line 8405
yield "<script src=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("js/sidebar-ecommerce-pro.js"), "html", null, true);
yield "\"></script>
";
$__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 ( 9324 => 8405, 9319 => 8401, 9315 => 8398, 9310 => 8394, 9306 => 8391, 9241 => 8326, 9235 => 8324, 9233 => 8323, 9037 => 8130, 9031 => 8127, 9025 => 8124, 8983 => 8085, 8977 => 8082, 8971 => 8079, 8519 => 7630, 8511 => 7624, 8506 => 7621, 8504 => 7620, 8428 => 7546, 8425 => 7545, 8413 => 7535, 8410 => 7534, 8408 => 7533, 8024 => 7152, 7954 => 7085, 7796 => 6930, 7785 => 6922, 7774 => 6914, 7756 => 6899, 7108 => 6254, 7038 => 6187, 7027 => 6179, 6578 => 5733, 6553 => 5711, 6497 => 5658, 6361 => 5525, 6300 => 5467, 6288 => 5458, 6036 => 5210, 6023 => 5209, 6009 => 5206, 5996 => 5196, 5987 => 5190, 5976 => 5182, 5969 => 5177, 5967 => 5176, 3625 => 2837, 3513 => 2727, 3497 => 2714, 3395 => 2614, 3392 => 2613, 3378 => 2612, 3372 => 2608, 3368 => 2606, 3364 => 2604, 3358 => 2603, 3354 => 2601, 3347 => 2597, 3343 => 2595, 3341 => 2594, 3334 => 2590, 3328 => 2586, 3325 => 2585, 3321 => 2584, 3314 => 2579, 3312 => 2578, 3310 => 2577, 3306 => 2575, 3300 => 2574, 3296 => 2572, 3289 => 2568, 3285 => 2566, 3283 => 2565, 3276 => 2561, 3270 => 2557, 3267 => 2556, 3263 => 2555, 3260 => 2554, 3258 => 2553, 3256 => 2552, 3252 => 2550, 3246 => 2549, 3242 => 2547, 3235 => 2543, 3231 => 2541, 3229 => 2540, 3222 => 2536, 3216 => 2532, 3213 => 2531, 3209 => 2530, 3206 => 2529, 3204 => 2528, 3202 => 2527, 3196 => 2524, 3190 => 2520, 3188 => 2519, 3183 => 2516, 3175 => 2513, 3171 => 2512, 3165 => 2510, 3163 => 2509, 3155 => 2504, 3147 => 2499, 3144 => 2498, 3142 => 2497, 3137 => 2494, 3130 => 2490, 3124 => 2488, 3122 => 2487, 3106 => 2473, 3103 => 2472, 3099 => 2471, 3091 => 2465, 3087 => 2464, 3082 => 2462, 3078 => 2461, 3074 => 2460, 3070 => 2459, 3066 => 2458, 3056 => 2451, 3048 => 2448, 3041 => 2444, 3038 => 2443, 3035 => 2442, 3031 => 2441, 3016 => 2428, 3008 => 2426, 3002 => 2424, 2998 => 2422, 2996 => 2421, 2993 => 2420, 2989 => 2419, 2982 => 2415, 2974 => 2410, 2966 => 2405, 2950 => 2392, 2930 => 2374, 2924 => 2371, 2921 => 2370, 2919 => 2369, 2916 => 2368, 2910 => 2365, 2907 => 2364, 2905 => 2363, 2902 => 2362, 2896 => 2359, 2893 => 2358, 2891 => 2357, 2880 => 2348, 2878 => 2340, 2843 => 2310, 2839 => 2309, 2836 => 2308, 2833 => 2307, 2830 => 2306, 2827 => 2305, 2824 => 2304, 2821 => 2303, 2803 => 2302, 2800 => 2301, 2798 => 2300, 2795 => 2299, 2778 => 2283, 2764 => 2282, 2758 => 2278, 2752 => 2276, 2749 => 2275, 2743 => 2273, 2740 => 2272, 2734 => 2270, 2732 => 2269, 2727 => 2266, 2725 => 2261, 2720 => 2258, 2718 => 2254, 2713 => 2251, 2707 => 2247, 2705 => 2246, 2700 => 2243, 2697 => 2242, 2694 => 2240, 2688 => 2237, 2683 => 2235, 2679 => 2234, 2676 => 2233, 2671 => 2230, 2669 => 2229, 2665 => 2228, 2661 => 2227, 2657 => 2225, 2654 => 2224, 2651 => 2223, 2648 => 2222, 2645 => 2221, 2642 => 2220, 2639 => 2219, 2636 => 2218, 2633 => 2217, 2630 => 2216, 2627 => 2215, 2624 => 2214, 2621 => 2212, 2618 => 2211, 2615 => 2210, 2612 => 2209, 2609 => 2208, 2606 => 2207, 2604 => 2206, 2602 => 2205, 2599 => 2204, 2596 => 2203, 2593 => 2202, 2590 => 2201, 2587 => 2200, 2570 => 2199, 2548 => 2179, 2542 => 2177, 2538 => 2175, 2536 => 2174, 2526 => 2171, 2523 => 2170, 2519 => 2169, 2511 => 2168, 2504 => 2166, 2496 => 2161, 2488 => 2158, 2478 => 2151, 2474 => 2150, 2470 => 2149, 2464 => 2146, 2449 => 2133, 2447 => 2132, 2443 => 2130, 2428 => 2118, 2416 => 2109, 2408 => 2103, 2406 => 2102, 2398 => 2097, 2395 => 2096, 2390 => 2093, 2383 => 2091, 2380 => 2090, 2378 => 2089, 2372 => 2085, 2369 => 2084, 2365 => 2081, 2362 => 2080, 2359 => 2079, 2356 => 2078, 2353 => 2076, 2350 => 2075, 2196 => 1923, 2192 => 1922, 2188 => 1921, 2160 => 1896, 2136 => 1875, 2129 => 1871, 2105 => 1850, 2098 => 1846, 2057 => 1808, 2029 => 1783, 2018 => 1775, 2014 => 1774, 2009 => 1771, 2007 => 1770, 1999 => 1764, 1986 => 1762, 1839 => 1627, 1835 => 1626, 1831 => 1625, 1827 => 1624, 1823 => 1623, 1819 => 1622, 1815 => 1621, 1811 => 1620, 1802 => 1614, 1797 => 1612, 1791 => 1609, 1787 => 1608, 1783 => 1607, 1779 => 1606, 1773 => 1603, 1769 => 1602, 1765 => 1601, 1761 => 1600, 1757 => 1599, 1753 => 1598, 1749 => 1597, 1743 => 1594, 1739 => 1593, 1735 => 1592, 1731 => 1591, 1727 => 1590, 1721 => 1587, 1716 => 1584, 1713 => 1583, 1710 => 1582, 1707 => 1581, 1704 => 1580, 1701 => 1579, 1698 => 1578, 1695 => 1577, 1692 => 1576, 1689 => 1575, 137 => 24, 135 => 23, 133 => 22, 131 => 21, 129 => 20, 127 => 19, 125 => 18, 123 => 17, 121 => 16, 119 => 15, 116 => 13, 114 => 12, 110 => 11, 106 => 10, 102 => 9, 98 => 8, 94 => 7, 90 => 6, 86 => 5, 80 => 2, 57 => 1, 55 => 1762, 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') }}\" />
<link rel=\"stylesheet\" href=\"{{ asset('css/sidebar-ecommerce-pro.css') }}\">
{% set destination = \"detailsejour\" %}
{# ==================== Configuration B2C Conversion ==================== #}
{% set nowTs = 'now'|date('U') %}
{% set debutTs = sejour.dateDebutSejour|date('U') %}
{% set finTs = sejour.dateFinSejour|date('U') %}
{% set closeTs = sejour.dateFinCode ? sejour.dateFinCode|date('U') : finTs %}
{% set secondsPerDay = 86400 %}
{% set daysSinceStart = ((nowTs - debutTs) / secondsPerDay)|round(0, 'floor') %}
{% set daysUntilEnd = ((finTs - nowTs) / secondsPerDay)|round(0, 'ceil') %}
{% set daysAfterEnd = ((nowTs - finTs) / secondsPerDay)|round(0, 'floor') %}
{% set daysUntilClose = ((closeTs - nowTs) / secondsPerDay)|round(0, 'ceil') %}
<style>
/* ============================================
PREMIUM DAY CARDS - Style Accompagnateur
============================================ */
/* Base card styles */
/* ============================================
PREMIUM DAY CARDS - Timeline ultra-premium
Identique à l'espace accompagnateur
============================================ */
.date-card {
flex: 0 0 auto;
width: 110px;
background: #fff;
border-radius: 14px;
padding: 8px 6px;
text-align: center;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
scroll-snap-align: center;
position: relative;
min-height: 56px;
max-height: 68px;
border: 1px solid transparent;
cursor: pointer;
}
.date-card .day {
font-weight: 600;
font-size: 0.70rem;
color: #2c3e50;
margin-bottom: 1px;
letter-spacing: -0.01em;
line-height: 1.15;
}
.date-card .full-date {
font-size: 0.60rem;
color: #6b7280;
font-weight: 500;
line-height: 1.1;
margin-top: 0px;
padding-bottom: 5px;
}
.date-card ul {
margin: 4px 0 0 0;
padding: 0;
list-style: none;
display: flex;
gap: 4px;
justify-content: center;
flex-wrap: wrap;
align-items: center;
}
.date-card ul li {
font-size: 0.60rem;
color: #6b7280;
display: flex;
align-items: center;
gap: 2px;
line-height: 1;
font-weight: 500;
}
.date-card ul li i {
font-size: 0.65rem;
}
.premium-day-card {
flex: 0 0 auto;
width: 130px;
background: #fff;
border-radius: 14px;
padding: 8px 6px;
text-align: center;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: visible;
scroll-snap-align: center;
position: relative;
min-height: 56px;
max-height: 68px;
border: 1px solid transparent;
cursor: pointer;
will-change: transform;
contain: layout style;
}
.premium-day-card .day {
font-weight: 600;
font-size: 0.9rem;
color: #2c3e50;
margin-bottom: 1px;
letter-spacing: -0.01em;
line-height: 1.15;
}
.premium-day-card .full-date {
font-size: 0.75rem;
color: #6b7280;
font-weight: 500;
line-height: 1.1;
margin-top: 0;
}
/* Compteurs de médias - Points colorés minimalistes */
.premium-day-card ul,
.date-card ul,
.media-list-horizontal {
margin: 4px 0 0 0;
padding: 0;
list-style: none;
display: flex;
gap: 4px;
justify-content: center;
flex-wrap: wrap;
align-items: center;
}
.premium-day-card ul li,
.date-card ul li {
font-size: 0.60rem;
color: #6b7280;
display: flex;
align-items: center;
gap: 2px;
line-height: 1;
font-weight: 500;
}
.premium-day-card ul li i,
.date-card ul li i {
font-size: 0.65rem;
}
/* ============================================
COULEURS PAR TYPE DE MÉDIA - Uniforme pour toutes les cartes
Photo = Orange, Vocal = Bleu, Vidéo = Orange clair
============================================ */
/* Photo = Orange 🟠 */
.premium-day-card ul li i.bi-camera,
.premium-day-card ul li i.bi-camera-fill,
.premium-day-card ul li i.bi-image,
.premium-day-card ul li i.bi-images,
.premium-day-past ul li i.bi-camera,
.premium-day-past ul li i.bi-camera-fill,
.premium-day-past ul li i.bi-image,
.premium-day-past ul li i.bi-images,
.premium-day-current ul li i.bi-camera,
.premium-day-current ul li i.bi-camera-fill,
.premium-day-current ul li i.bi-image,
.premium-day-current ul li i.bi-images,
.premium-day-future ul li i.bi-camera,
.premium-day-future ul li i.bi-camera-fill,
.premium-day-future ul li i.bi-image,
.premium-day-future ul li i.bi-images,
.date-card ul li i.bi-camera,
.date-card ul li i.bi-camera-fill,
.date-card ul li i.bi-image,
.date-card ul li i.bi-images {
color: #F56040 !important;
opacity: 1 !important;
}
/* Vocal/Audio = Bleu Mint 🔵 */
.premium-day-card ul li i.bi-mic-fill,
.premium-day-card ul li i.bi-mic,
.premium-day-past ul li i.bi-mic-fill,
.premium-day-past ul li i.bi-mic,
.premium-day-current ul li i.bi-mic-fill,
.premium-day-current ul li i.bi-mic,
.premium-day-future ul li i.bi-mic-fill,
.premium-day-future ul li i.bi-mic,
.date-card ul li i.bi-mic-fill,
.date-card ul li i.bi-mic {
color: #41A2AA !important;
opacity: 1 !important;
}
/* Vidéo = Orange clair 🟠 */
.premium-day-card ul li i.bi-camera-video,
.premium-day-card ul li i.bi-camera-video-fill,
.premium-day-card ul li i.bi-film,
.premium-day-past ul li i.bi-camera-video,
.premium-day-past ul li i.bi-camera-video-fill,
.premium-day-past ul li i.bi-film,
.premium-day-current ul li i.bi-camera-video,
.premium-day-current ul li i.bi-camera-video-fill,
.premium-day-current ul li i.bi-film,
.premium-day-future ul li i.bi-camera-video,
.premium-day-future ul li i.bi-camera-video-fill,
.premium-day-future ul li i.bi-film,
.date-card ul li i.bi-camera-video,
.date-card ul li i.bi-camera-video-fill,
.date-card ul li i.bi-film {
color: #FF9F7A !important;
opacity: 1 !important;
}
/* État PASSÉ - Cohérent avec charte 5sur5 */
.premium-day-past {
opacity: 0.85;
background: rgba(65, 162, 170, 0.05);
border: 1px solid rgba(65, 162, 170, 0.25);
cursor: pointer;
pointer-events: auto;
}
.premium-day-past:hover {
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
opacity: 1;
}
/* État AUJOURD'HUI - Premium mis en avant clairement */
.premium-day-current {
width: 110px !important;
border: 2px solid #41A2AA !important;
background: #fff !important;
box-shadow: 0 6px 16px rgba(65, 162, 170, 0.12) !important;
transform: scale(1.04);
z-index: 10;
cursor: pointer;
opacity: 1 !important;
position: relative;
}
/* Point animé \"Aujourd'hui\" - style calendrier moderne */
.premium-day-current::after {
content: '';
position: absolute;
top: 8px;
right: 8px;
width: 8px;
height: 8px;
background: #41A2AA;
border-radius: 50%;
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0.7);
animation: todayPulse 2s ease-in-out infinite;
z-index: 5;
}
@keyframes todayPulse {
0% {
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0.7);
}
50% {
box-shadow: 0 0 0 6px rgba(65, 162, 170, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(65, 162, 170, 0);
}
}
.premium-day-current:hover {
transform: scale(1.04);
box-shadow: 0 6px 16px rgba(65, 162, 170, 0.12) !important;
}
.premium-day-current:focus-visible {
outline: 3px solid rgba(65, 162, 170, 0.3);
outline-offset: 2px;
}
.premium-day-current .day,
.premium-day-current .full-date {
color: #137F86 !important;
font-weight: 600 !important;
}
.premium-day-current .full-date {
padding-bottom: 5px !important;
}
.premium-day-current ul li {
color: #137F86 !important;
}
/* État ACTIF - Carte sélectionnée */
.date-card.active,
.premium-day-card.active {
width: 140px !important;
border: 2px solid #41A2AA !important;
background: #EAF7F7 !important;
box-shadow: 0 2px 8px rgba(65, 162, 170, 0.15) !important;
transform: scale(1.02);
z-index: 10;
}
.date-card.active .day,
.premium-day-card.active .day,
.date-card.active .day-number,
.premium-day-card.active .day-number {
color: #137F86 !important;
font-weight: 700;
}
.date-card.active .full-date,
.premium-day-card.active .full-date,
.date-card.active .day-name,
.premium-day-card.active .day-name {
color: #137F86 !important;
font-weight: 700;
}
.date-card.active ul li,
.premium-day-card.active ul li {
color: #137F86 !important;
}
.date-card.active ul li i,
.premium-day-card.active ul li i {
color: #41A2AA !important;
}
.date-card:hover,
.premium-day-card:hover {
background-color: #f9f9f9;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* État FUTUR - Semi-caché, réduit, verrouillé */
.premium-day-future {
opacity: 0.45 !important;
transform: scale(0.92);
cursor: not-allowed !important;
pointer-events: none !important;
border: 1px solid rgba(188, 196, 206, 0.2) !important;
background: #fff !important;
box-shadow: none !important;
}
.premium-day-future:hover {
transform: scale(0.92) !important;
box-shadow: none !important;
}
.premium-day-future .day,
.premium-day-future .full-date {
color: #BCC4CE !important;
}
.premium-day-future ul {
display: none !important;
}
/* Icône cadenas sur jours futurs */
.premium-day-future::before {
content: '\\F4C0';
font-family: 'bootstrap-icons';
position: absolute;
top: 4px;
right: 4px;
font-size: 9px;
color: #BCC4CE;
opacity: 0.25;
}
/* Tooltip pour jours futurs */
.premium-day-future::after {
content: attr(data-tooltip);
position: absolute;
bottom: -35px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85);
color: white;
padding: 6px 10px;
border-radius: 6px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
z-index: 1000;
}
/* Date container scroll */
.date-container {
display: flex;
gap: 12px;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
padding: 8px 4px;
}
.date-container::-webkit-scrollbar {
display: none;
}
/* Section days wrapper */
.section-days {
background: #f8fafb;
border-radius: 12px;
padding: 10px 12px;
margin-bottom: 16px;
border: 1px solid rgba(65, 162, 170, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* Bouton favoris désactivé (0 favoris) */
.mtb-btn.favoris-empty,
.mtb-btn[data-filter=\"favoris\"]:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
background: rgba(0, 0, 0, 0.02) !important;
border-color: rgba(0, 0, 0, 0.08) !important;
}
.mtb-btn.favoris-empty:hover,
.mtb-btn[data-filter=\"favoris\"]:disabled:hover {
transform: none;
box-shadow: none;
}
.favoris-empty-text {
font-size: 11px;
font-family: inherit;
color: #94a3b8;
font-style: italic;
font-weight: 500;
line-height: 1.2;
}
/* Styles premium pour les filtres */
.premium-5sur5-actions-group {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.premium-5sur5-action-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 12px;
background: #F7FBFC;
border: 1px solid rgba(65, 162, 170, 0.12);
border-radius: 14px;
color: #41A2AA;
font-size: 13px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
position: relative;
line-height: 1.4;
}
.premium-5sur5-action-btn:hover:not(:disabled) {
background: rgba(65, 162, 170, 0.08);
border-color: rgba(65, 162, 170, 0.25);
transform: translateY(-1px);
}
.premium-5sur5-action-btn:active:not(:disabled) {
transform: translateY(0);
}
.premium-5sur5-action-btn i {
font-size: 14px;
opacity: 0.85;
line-height: 1;
}
.premium-5sur5-action-btn span:not(.premium-5sur5-badge):not(.favoris-empty-text) {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.2px;
line-height: 1.4;
font-family: inherit;
}
.premium-5sur5-badge {
display: inline-block;
font-size: 11px;
font-weight: 600;
font-family: inherit;
padding: 2px 6px;
border-radius: 6px;
letter-spacing: 0.2px;
margin-left: 2px;
line-height: 1.2;
}
/* État actif pour les boutons premium */
.premium-5sur5-action-btn.mtb-btn.active,
.premium-5sur5-action-btn.mtb-btn.is-active {
background: rgba(65, 162, 170, 0.15) !important;
border-color: #41A2AA !important;
color: #137F86 !important;
box-shadow: 0 2px 8px rgba(65, 162, 170, 0.2);
}
.premium-5sur5-action-btn.mtb-btn[data-filter=\"favoris\"].active {
background: rgba(245, 96, 64, 0.15) !important;
border-color: #f56040 !important;
color: #d43e1f !important;
}
.premium-5sur5-action-btn.mtb-btn[data-filter=\"audio\"].active {
background: rgba(255, 215, 0, 0.15) !important;
border-color: #ffd700 !important;
color: #b8860b !important;
}
/* Bouton favoris désactivé */
.premium-5sur5-action-btn.favoris-empty,
.premium-5sur5-action-btn[data-filter=\"favoris\"]:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
background: rgba(0, 0, 0, 0.02) !important;
border-color: rgba(0, 0, 0, 0.08) !important;
}
.premium-5sur5-action-btn.favoris-empty:hover,
.premium-5sur5-action-btn[data-filter=\"favoris\"]:disabled:hover {
transform: none;
box-shadow: none;
}
/* Styles pour la section favoris */
.favorites-ecommerce-view {
width: 100%;
max-width: 100%;
padding: 24px;
box-sizing: border-box;
}
.favorites-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 20px;
background: #f8fafb;
border-radius: 12px;
border: 1px solid rgba(65, 162, 170, 0.1);
}
.favorites-title {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.favorites-title h3 {
font-size: 20px;
font-weight: 700;
color: #1a1a1a;
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.favorites-title h3 i {
color: #f56040;
font-size: 20px;
}
.favorites-count {
display: inline-flex;
align-items: center;
padding: 6px 12px;
background: rgba(245, 96, 64, 0.1);
color: #f56040;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
}
.btn-back-to-days {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #ffffff;
border: 1px solid rgba(65, 162, 170, 0.2);
border-radius: 10px;
color: #6b7280;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-back-to-days:hover {
background: rgba(65, 162, 170, 0.05);
border-color: #41A2AA;
color: #41A2AA;
}
.favorites-content {
width: 100%;
}
.favorites-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
width: 100%;
padding: 0;
}
.favorites-grid .favorite-item,
.favorites-grid .photo-item {
position: relative;
width: 100%;
aspect-ratio: 1;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
cursor: pointer;
}
.favorites-grid .favorite-item:hover,
.favorites-grid .photo-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.favorites-grid .favorite-item:hover .favorite-actions,
.favorites-grid .photo-item:hover .favorite-actions {
opacity: 1;
}
.favorites-grid img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.favorites-grid .favorite-item {
position: relative;
}
.favorites-grid .favorite-actions {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 6px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 10;
}
/* CTA discret et premium */
.premium-cta-subtle {
margin: 20px 0;
padding: 16px 20px;
background: rgba(65, 162, 170, 0.04);
border: 1px solid rgba(65, 162, 170, 0.15);
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
text-decoration: none;
color: inherit;
}
.premium-cta-subtle:hover {
background: rgba(65, 162, 170, 0.08);
border-color: rgba(65, 162, 170, 0.25);
transform: translateX(2px);
text-decoration: none;
color: inherit;
}
.premium-cta-subtle > div:first-child {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.premium-cta-subtle > div:first-child > div:first-child {
width: 40px;
height: 40px;
background: rgba(65, 162, 170, 0.1);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.premium-cta-subtle > div:first-child > div:first-child i {
font-size: 18px;
color: #41A2AA;
}
.premium-cta-subtle > div:first-child > div:last-child {
flex: 1;
min-width: 0;
}
.premium-cta-subtle > div:first-child > div:last-child p:first-child {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
}
.premium-cta-subtle > div:first-child > div:last-child p:last-child {
margin: 4px 0 0 0;
font-size: 12px;
color: #6b7280;
line-height: 1.4;
}
.premium-cta-subtle > i:last-child {
font-size: 18px;
color: #41A2AA;
flex-shrink: 0;
}
.premium-5sur5-day-title {
font-size: 1.35rem;
font-weight: 700;
color: #2c3e50;
margin: 0 0 4px 0;
letter-spacing: -0.02em;
line-height: 1.3;
}
.premium-5sur5-day-subtitle {
font-size: 0.9rem;
color: #6b7280;
margin: 0;
font-weight: 500;
line-height: 1.5;
}
@media (max-width: 768px) {
.premium-5sur5-day-header {
padding: 12px 16px;
margin-bottom: 16px;
}
.premium-5sur5-day-title {
font-size: 1.15rem;
}
.premium-5sur5-day-subtitle {
font-size: 0.8rem;
}
/* Menu sticky en bas - Override des styles précédents */
.premium-5sur5-actions-group {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
width: 100% !important;
background: #ffffff !important;
border-top: 1px solid rgba(65, 162, 170, 0.15) !important;
padding: 12px 16px !important;
z-index: 1050 !important;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08) !important;
justify-content: space-around !important;
gap: 0 !important;
margin: 0 !important;
margin-top: 0 !important;
/* Safe area pour iPhone avec encoche */
padding-bottom: max(12px, env(safe-area-inset-bottom)) !important;
}
.premium-5sur5-action-btn {
flex: 1 !important;
justify-content: center !important;
min-width: 0 !important;
padding: 10px 8px !important;
border-radius: 12px !important;
font-size: 12px !important;
}
.premium-5sur5-action-btn i {
font-size: 16px !important;
}
.premium-5sur5-action-btn span:not(.premium-5sur5-badge):not(.favoris-empty-text) {
font-size: 12px !important;
}
.premium-5sur5-badge {
font-size: 10px !important;
padding: 2px 5px !important;
}
/* Ajouter un padding-bottom au contenu pour éviter que le menu sticky ne cache le contenu */
#scrollTarget {
padding-bottom: 80px !important;
}
/* Section favoris responsive */
.favorites-ecommerce-view {
padding: 16px !important;
}
.favorites-header {
flex-direction: column !important;
gap: 12px !important;
align-items: flex-start !important;
padding: 16px !important;
margin-bottom: 16px !important;
}
.favorites-title {
width: 100% !important;
}
.favorites-title h3 {
font-size: 18px !important;
margin-bottom: 8px !important;
}
.favorites-count {
font-size: 13px !important;
padding: 4px 10px !important;
}
.btn-back-to-days {
width: 100% !important;
justify-content: center !important;
padding: 10px 16px !important;
font-size: 14px !important;
}
.favorites-content {
padding: 0 !important;
}
.favorites-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 12px !important;
padding: 0 !important;
}
.favorites-grid .favorite-item,
.favorites-grid .photo-item {
width: 100% !important;
height: auto !important;
min-height: 150px !important;
aspect-ratio: 1 !important;
position: relative !important;
display: block !important;
border-radius: 12px !important;
overflow: hidden !important;
background: #f0f0f0 !important;
}
.favorites-grid .favorite-item img,
.favorites-grid .photo-item img {
width: 100% !important;
height: 100% !important;
min-height: 150px !important;
object-fit: cover !important;
display: block !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
border-radius: 12px !important;
}
.favorites-grid video {
width: 100% !important;
height: 100% !important;
min-height: 150px !important;
object-fit: cover !important;
display: block !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
border-radius: 12px !important;
}
/* CTA discret responsive */
.premium-cta-subtle {
padding: 12px 16px !important;
margin: 16px 0 !important;
}
.premium-cta-subtle > div:first-child > div:first-child {
width: 36px !important;
height: 36px !important;
}
.premium-cta-subtle > div:first-child > div:first-child i {
font-size: 16px !important;
}
.premium-cta-subtle > div:first-child > div:last-child p:first-child {
font-size: 13px !important;
}
.premium-cta-subtle > div:first-child > div:last-child p:last-child {
font-size: 11px !important;
}
.premium-cta-subtle:hover {
background: rgba(65, 162, 170, 0.08) !important;
border-color: rgba(65, 162, 170, 0.25) !important;
transform: translateX(2px) !important;
}
}
.date-navigation {
width: 100%;
}
/* Legacy compatibility */
.date-card .card-content {
display: block;
text-align: center;
}
.date-card .media-list-horizontal {
margin: 0 !important;
padding: 0 !important;
display: flex;
gap: 6px;
justify-content: center;
}
.date-card .media-list-horizontal li {
font-size: 0.65rem !important;
}
/* Dots minimalistes pour les indicateurs de jour */
.dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-left: 4px;
}
.dot.blue {
background: #41a2aa;
}
.dot.green {
background: #8ed081;
}
.dot.orange {
background: #ff9c57;
}
/* Dot multi-couleur pour aujourd'hui + premier/dernier jour */
.status-dot.multi {
position: absolute;
top: 6px;
right: 6px;
width: 10px;
height: 10px;
border-radius: 50%;
background: linear-gradient(90deg, #41a2aa 50%, #ff9c57 50%);
z-index: 5;
}
/* Variante pour premier jour + aujourd'hui */
.status-dot.multi.green-orange {
background: linear-gradient(90deg, #8ed081 50%, #ff9c57 50%);
}
/* ==================== TOASTS & ANIMATIONS ==================== */
@keyframes slideUpFromGift {
0% {
opacity: 0;
transform: translateY(60px) scale(0.9);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
/* ========== BANDEAU PROMO SÉJOUR TERMINÉ ========== */
.promo-banner-termine {
background: linear-gradient(90deg, #d32f2f 0%, #e65100 50%, #ff5722 100%);
color: white;
padding: 0;
height: 38px;
width: 100%;
position: relative;
z-index: 100;
box-shadow: 0 2px 6px rgba(211, 47, 47, 0.2);
}
.promo-banner-content {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
max-width: 1200px;
margin: 0 auto;
padding: 0 12px;
height: 100%;
}
.promo-banner-icon {
font-size: 16px;
flex-shrink: 0;
}
.promo-banner-text {
font-size: 12px;
line-height: 1.2;
}
.promo-banner-text strong {
font-weight: 700;
display: inline;
}
.promo-days {
color: #fff;
font-weight: 700;
background: rgba(255,255,255,0.2);
padding: 1px 5px;
border-radius: 3px;
font-size: 11px;
}
.promo-banner-cta {
background: #fff;
color: #d32f2f;
font-weight: 600;
font-size: 11px;
padding: 4px 12px;
border-radius: 12px;
text-decoration: none;
white-space: nowrap;
transition: all 0.2s;
}
.promo-banner-cta:hover {
background: rgba(255,255,255,0.9);
transform: scale(1.02);
color: #d32f2f;
}
.promo-banner-close {
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
opacity: 0.7;
padding: 0 2px;
margin-left: 2px;
line-height: 1;
}
.promo-banner-close:hover {
opacity: 1;
}
@media (max-width: 600px) {
.promo-banner-termine {
height: 34px;
}
.promo-banner-content {
gap: 6px;
padding: 0 8px;
}
.promo-banner-text {
font-size: 11px;
}
.promo-banner-icon {
font-size: 14px;
}
}
/* ========== SLIDER COMPACT PREMIUM ========== */
#heroPromo {
margin: 0;
padding: 0;
width: 100%;
overflow: hidden;
background: #fff;
}
#heroPromo .divSliderModern {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider {
max-height: 70px !important;
height: 70px !important;
overflow: hidden !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__track {
height: 70px !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__list {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__slide {
height: 70px !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
#imageSlider .splide__slide img {
object-fit: contain !important;
height: auto !important;
width: auto !important;
}
.slider-content-compact {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0;
background: linear-gradient(135deg, #f7fcfc 0%, #ffffff 30%, #fef8f5 100%);
height: 70px !important;
padding: 0;
width: 100%;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
object-fit: cover;
flex-shrink: 0;
display: block;
}
.slider-text-compact {
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-start;
justify-content: center;
flex: 1;
min-width: 0;
overflow: hidden;
padding: 12px 24px;
height: 100%;
}
.slider-title-compact {
font-size: 14px;
font-weight: 700;
line-height: 1.3;
color: inherit;
white-space: normal;
word-wrap: break-word;
max-width: 100%;
}
.slider-subtitle-compact {
font-size: 12px;
font-weight: 500;
line-height: 1.3;
color: inherit;
white-space: normal;
word-wrap: break-word;
max-width: 100%;
}
@media (max-width: 768px) {
#imageSlider, #imageSlider .splide__track, #imageSlider .splide__slide, .slider-content-compact {
height: 65px !important;
}
.slider-content-compact {
gap: 0;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
}
.slider-text-compact {
padding: 10px 16px;
}
.slider-title-compact {
font-size: 13px;
line-height: 1.2;
}
.slider-subtitle-compact {
font-size: 11px;
line-height: 1.2;
}
}
@media (max-width: 480px) {
#imageSlider, #imageSlider .splide__track, #imageSlider .splide__slide, .slider-content-compact {
height: 60px !important;
}
.slider-content-compact {
gap: 0;
}
.imgslider-compact {
height: 100% !important;
width: 70% !important;
}
.slider-text-compact {
padding: 8px 12px;
}
.slider-title-compact {
font-size: 12px;
line-height: 1.2;
}
.slider-subtitle-compact {
font-size: 10px;
line-height: 1.2;
}
}
/* ========== EMPTY STATE ========== */
.stay-empty-wrapper {
padding: 50px 20px 60px;
display: flex;
justify-content: center;
}
.stay-empty-card {
max-width: 720px;
width: 100%;
background: #ffffff;
border-radius: 24px;
padding: 44px 40px 40px;
box-shadow: 0 16px 50px rgba(5, 45, 60, 0.06);
margin: 0 auto;
}
.stay-empty-title {
font-size: 1.5rem;
font-weight: 700;
color: #1A1A2E;
margin-bottom: 20px;
text-align: center;
}
.stay-empty-intro {
font-size: 0.98rem;
line-height: 1.7;
color: #5A6178;
max-width: 580px;
margin: 0 auto 32px;
text-align: center;
}
/* Section \"Ce qui va arriver\" */
.stay-empty-expect {
display: flex;
gap: 36px;
align-items: flex-start;
margin-bottom: 28px;
}
.stay-empty-expect-content {
flex: 1;
}
.stay-empty-expect-label {
font-size: 0.92rem;
font-weight: 600;
color: #1A1A2E;
margin-bottom: 16px;
}
.stay-empty-list {
list-style: none;
padding: 0;
margin: 0;
}
.stay-empty-list li {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 0;
font-size: 0.92rem;
color: #4B5563;
line-height: 1.5;
}
.stay-empty-list-icon {
width: 28px;
height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
}
.stay-empty-list-icon svg {
width: 14px;
height: 14px;
}
.stay-empty-list-icon--check {
background: rgba(65, 162, 170, 0.12);
color: #41A2AA;
}
.stay-empty-list-icon--photos {
background: rgba(240, 158, 122, 0.12);
color: #E8865E;
}
.stay-empty-list-icon--vocal {
background: rgba(155, 139, 244, 0.12);
color: #7B6BE0;
}
/* Illustration compacte */
.stay-empty-illustration {
position: relative;
width: 180px;
height: 200px;
flex-shrink: 0;
}
.stay-empty-phone {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 90px;
filter: drop-shadow(0 8px 20px rgba(0,0,0,0.1));
animation: phoneFloat 4s ease-in-out infinite;
}
@keyframes phoneFloat {
0%, 100% { transform: translate(-50%, -50%) translateY(0); }
50% { transform: translate(-50%, -50%) translateY(-5px); }
}
.stay-empty-bubble {
position: absolute;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 14px rgba(0,0,0,0.08);
font-size: 0.72rem;
font-weight: 600;
color: #1A1A2E;
white-space: nowrap;
opacity: 0;
animation: bubbleFadeIn 0.5s ease forwards;
}
.stay-empty-bubble-icon {
width: 18px;
height: 18px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stay-empty-bubble-icon svg {
width: 10px;
height: 10px;
}
.stay-empty-bubble--arrived {
top: 20px;
left: -10px;
animation-delay: 0.4s;
}
.stay-empty-bubble--arrived .stay-empty-bubble-icon {
background: linear-gradient(135deg, #41A2AA, #359BA3);
color: white;
}
.stay-empty-bubble--photos {
top: 70px;
right: -5px;
animation-delay: 0.9s;
}
.stay-empty-bubble--photos .stay-empty-bubble-icon {
background: linear-gradient(135deg, #F09E7A, #E8865E);
color: white;
}
@keyframes bubbleFadeIn {
0% { opacity: 0; transform: translateY(6px) scale(0.95); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
.stay-empty-illustration-label {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
color: #8E95A9;
white-space: nowrap;
text-align: center;
}
/* Note rassurante */
.stay-empty-reassurance {
background: #F8FAFB;
border-radius: 12px;
padding: 14px 20px;
margin-bottom: 24px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.stay-empty-reassurance-icon {
width: 20px;
height: 20px;
color: #41A2AA;
flex-shrink: 0;
margin-top: 2px;
}
.stay-empty-reassurance p {
margin: 0;
font-size: 0.85rem;
color: #5A6178;
line-height: 1.6;
}
/* CTA */
.stay-empty-cta {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 22px;
font-size: 0.88rem;
font-weight: 600;
color: #41A2AA;
background: transparent;
border: 1.5px solid #E5E9F0;
border-radius: 10px;
text-decoration: none;
transition: all 0.2s ease;
}
.stay-empty-cta:hover {
background: #F0F9FA;
border-color: #41A2AA;
color: #359BA3;
text-decoration: none;
}
.stay-empty-cta svg {
width: 16px;
height: 16px;
}
.stay-empty-footer {
text-align: center;
}
/* Responsive */
@media (max-width: 700px) {
.stay-empty-card {
padding: 32px 24px 32px;
}
.stay-empty-title {
font-size: 1.3rem;
}
.stay-empty-expect {
flex-direction: column;
gap: 24px;
}
.stay-empty-illustration {
width: 100%;
height: 160px;
order: -1;
}
.stay-empty-phone {
width: 80px;
}
.stay-empty-bubble {
font-size: 0.68rem;
padding: 5px 8px;
}
.stay-empty-bubble--arrived {
left: calc(50% - 85px);
}
.stay-empty-bubble--photos {
right: calc(50% - 85px);
}
.stay-empty-reassurance {
flex-direction: column;
gap: 8px;
text-align: center;
}
.stay-empty-reassurance-icon {
margin: 0 auto;
}
}
</style>
<script>
window.__FEATURE_PARENT_CONVERSION__ = true;
// Configuration runtime pour le système B2C
{# Calcul des dates pour le JS - on calcule tout en Twig #}
{% set nowTimestamp = 'now'|date('U') %}
{% set debutTimestamp = sejour.dateDebutSejour|date('U') %}
{% set finTimestamp = sejour.dateFinSejour|date('U') %}
{% set finCodeDateJS = sejour.dateFinCode is defined and sejour.dateFinCode is not null ? sejour.dateFinCode : sejour.dateFinSejour|date_modify('+42 days') %}
{% set closeTimestamp = finCodeDateJS|date('U') %}
{% set daysSinceStartCalc = ((nowTimestamp - debutTimestamp) / 86400)|round(0, 'floor') %}
{% set daysUntilEndCalc = ((finTimestamp - nowTimestamp) / 86400)|round(0, 'ceil') %}
{% set daysAfterEndCalc = ((nowTimestamp - finTimestamp) / 86400)|round(0, 'floor') %}
{% set daysUntilCloseCalc = ((closeTimestamp - nowTimestamp) / 86400)|round(0, 'ceil') %}
window.PARENT_CONVERSION_CONFIG = {
user: {
prenom: \"{{ app.user.prenom|default('Parent')|e('js') }}\"
},
sejour: {
id: {{ sejour.id }},
dateDebut: \"{{ sejour.dateDebutSejour|date('Y-m-d') }}\",
dateFin: \"{{ sejour.dateFinSejour|date('Y-m-d') }}\",
dateFinCode: \"{{ sejour.dateFinCode ? sejour.dateFinCode|date('Y-m-d') : '' }}\",
isTermine: {{ (sejour.dateFinSejour < date()) ? 'true' : 'false' }}
},
dates: {
aujourdhui: \"{{ 'now'|date('Y-m-d') }}\",
isFirstDay: {{ (sejour.dateDebutSejour|date('Y-m-d') == 'now'|date('Y-m-d')) ? 'true' : 'false' }},
isWithin: {{ (sejour.dateDebutSejour <= date() and sejour.dateFinSejour >= date()) ? 'true' : 'false' }},
daysSinceStart: {{ daysSinceStartCalc|default(0) }},
daysUntilEnd: {{ daysUntilEndCalc|default(0) }},
daysAfterEnd: {{ daysAfterEndCalc|default(0) }},
daysUntilClose: {{ daysUntilCloseCalc|default(0) }}
},
stats: {
totalDays: {{ sejour.dateFinSejour.diff(sejour.dateDebutSejour).days }},
totalPhotos: {{ attachementsCount|default(0) }},
totalMessages: {{ nbmessages|default(0) }},
todayPhotos: {{ photosDuJour|default(0) }}
},
parent: {
favorites: {{ nblikes|default(0) }},
cartCount: 0,
lastVisitAt: \"{{ 'now'|date('Y-m-d H:i:s') }}\"
}
};
// Catalogue produits (routes réelles 5sur5)
window.PARENT_PRODUCT_CATALOG = {
digi10: { title:\"Pack numérique 10 photos\", badge:\"Petit budget\", url:\"{{ path('boutique5sur5') }}\" },
digi25: { title:\"Pack numérique 25 photos\", badge:\"Immédiat\", url:\"{{ path('boutique5sur5') }}\" },
retro12: { title:\"Pochette rétro 12 tirages\", badge:\"Cadeau\", url:\"{{ path('boutique5sur5') }}\" },
prints20: { title:\"Pochette 20 tirages\", badge:\"Best seller\", url:\"{{ path('boutique5sur5') }}\" },
album24: { title:\"Mini-album 24 pages\", badge:\"Parfait début\", url:\"{{ path('Album_du_Sejour') }}\" },
album48: { title:\"Album Premium 48 pages\", badge:\"Meilleure vente\", url:\"{{ path('Album_du_Sejour') }}\" },
mix10Poster: { title:\"Pack mixte 10 tirages + poster\", badge:\"Combo\", url:\"{{ path('boutique5sur5') }}\" },
bundleAlbumPrints: { title:\"Album + tirages (-15%)\", badge:\"Éco\", url:\"{{ path('boutique5sur5') }}\" }
};
</script>
<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;
}
/* === Badges discrets des cartes jours (style accompagnateur avec Bootstrap) === */
/* Les badges utilisent directement les classes Bootstrap: badge, bg-success, bg-info, position-absolute */
/* === Affichage des dates (style accompagnateur exact) === */
.container--gallery.modern {
background: #ffffff;
border-radius: 32px;
padding: 10px;
box-shadow: 0 30px 70px rgba(11, 36, 71, 0.08);
border: 1px solid rgba(65, 162, 170, 0.08);
min-height:1000px;
}
@media (max-width: 768px) {
.container--gallery.modern {
padding: 24px;
min-height:500px;
}
}
.pro-journal {
background: linear-gradient(145deg, #f8fbff 0%, #ffffff 100%);
border-radius: 24px;
padding: 28px;
margin-bottom: 28px;
border: 1px solid rgba(65, 162, 170, 0.08);
box-shadow: 0 18px 45px rgba(11, 36, 71, 0.05);
}
.pro-entry-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 18px;
margin-bottom: 20px;
}
.pro-entry-header .date-chip {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 18px;
border-radius: 999px;
background: rgba(65, 162, 170, 0.08);
color: #1f2933;
font-weight: 600;
font-size: 0.95rem;
}
.gallery-meta-chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.gallery-meta-chips .filter-badge {
background: #ffffff;
border: 1px solid rgba(15, 40, 77, 0.08);
border-radius: 999px;
padding: 8px 14px;
font-size: 0.85rem;
font-weight: 600;
color: #405064;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.gallery-meta-chips .filter-badge.active {
background: #41a2aa;
color: #fff;
border-color: #41a2aa;
box-shadow: 0 8px 20px rgba(65, 162, 170, 0.25);
}
.gallery-grid {
width: 100%;
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 18px;
}
.gallery-grid .column {
margin: 0 !important;
}
.gallery-grid .photo-item,
.gallery-grid .video-item,
.gallery-grid .audio-message-item {
border-radius: 18px;
background: #fff;
border: 1px solid rgba(15, 40, 77, 0.08);
box-shadow: 0 12px 30px rgba(11, 36, 71, 0.08);
}
.gallery-grid .photo-item img {
border-radius: 16px;
height: 220px;
object-fit: cover;
}
.gallery-grid .video-item video,
.gallery-grid .audio-message-item audio {
border-radius: 12px;
}
</style>
{% endblock %} {% set pageMenu = app.session.get('pageMenu') %} {% block Content
%}
<div class=\"main-content\">
<div class=\"row no-margin\">
<!-- Bouton cadeau attractif e-commerce -->
{% set showGiftButton = (likes|default([]))|length > 0 %}
<div
class=\"gift-button\"
id=\"gift-button-trigger\"
data-has-favorites=\"{{ showGiftButton ? '1' : '0' }}\"
style=\"cursor: pointer; {{ showGiftButton ? '' : 'display:none;' }}\"
>
<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|default([]))|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|default([]))|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> Les 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\" id=\"album-card\">
<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|default([]))|length }}</span>
</div>
</div>
<p class=\"product-description\">
Album photo personnalisé avec vos <strong id=\"album-count\">{{ (likes|default([]))|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\" id=\"digital-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|default([]))|length }}</span>
</div>
</div>
<p class=\"product-description\">
Téléchargement immédiat de vos <strong id=\"digital-count\">{{ (likes|default([]))|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 class=\"product-favorites-indicator\" title=\"Photos favorites sélectionnées\">
<i class=\"bi bi-heart-fill\" style=\"color: #f56040\"></i>
<span>{{ (likes|default([]))|length }}</span>
</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é en France – satisfaction garantie ✅</strong>
</div>
</div>
</div>
<div class=\"selection-popover\" id=\"selectionPopover\">
<h4>Votre sélection</h4>
<p>Tirages : {{ (likes|default([]))|length }} / 12</p>
<p>Numériques : {{ (likes|default([]))|length }} / 15</p>
<p>Album : {{ (likes|default([]))|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>
{# Zone promo - Masquée si pas de photos #}
{% if attachementsCount|default(0) > 0 %}
{# Calculer si séjour terminé #}
{% set isSejourTermine = sejour.dateFinSejour < date() %}
{% set finCodeDate = sejour.dateFinCode is defined and sejour.dateFinCode is not null ? sejour.dateFinCode : sejour.dateFinSejour|date_modify('+42 days') %}
{% set daysUntilClose = ((finCodeDate|date('U') - 'now'|date('U')) / 86400)|round(0, 'ceil') %}
<section id=\"heroPromo\" style=\"margin: 0; padding: 0;\">
{# BANDEAU SÉJOUR TERMINÉ - S'affiche pour tout séjour passé #}
{% if isSejourTermine %}
<div class=\"promo-banner-termine\" id=\"promoBannerTermine\">
<div class=\"promo-banner-content\">
<span class=\"promo-banner-icon\">🎬</span>
<div class=\"promo-banner-text\">
{% if daysUntilClose > 0 %}
<strong>Séjour terminé — Dernière chance !</strong>
Plus que <span class=\"promo-days\">{{ daysUntilClose }}</span> jour{{ daysUntilClose > 1 ? 's' : '' }} pour commander vos souvenirs.
{% else %}
<strong>Séjour terminé</strong>
Créez vos souvenirs : albums, tirages, photos numériques...
{% endif %}
</div>
<a href=\"{{ path('boutique5sur5') }}\" class=\"promo-banner-cta\">Commander</a>
<button class=\"promo-banner-close\" onclick=\"this.parentElement.parentElement.style.display='none'\" aria-label=\"Fermer\">×</button>
</div>
</div>
{% else %}
{# SLIDER PROMO PREMIUM - Séjour en cours #}
<div class=\"divSliderModern\" style=\"margin: 0; padding: 0; width: 100%; overflow: hidden;\">
<div class=\"splide no-padding no-margin\" id=\"imageSlider\" style=\"width: 100%;\">
<div class=\"splide__track\" style=\"width: 100%;\">
<ul class=\"splide__list\" style=\"width: 100%;\">
<li class=\"splide__slide\" style=\"width: 100%;\">
<div class=\"slider-content-compact\">
<img src=\"{{ asset('/images/imgSliderEmpty2.png') }}\" class=\"imgslider-compact\" alt=\"Produits souvenirs\" />
<div class=\"slider-text-compact\">
<span class=\"slider-title-compact\" style=\"color: #41a2aa\">Ajoutez vos favoris</span>
<span class=\"slider-subtitle-compact\" style=\"color: #f09e7a\">et créez vos souvenirs personnalisés !</span>
</div>
</div>
</li>
<li class=\"splide__slide\" style=\"width: 100%;\">
<div class=\"slider-content-compact\">
<img src=\"{{ asset('/images/imgSliderEmpty1.png') }}\" class=\"imgslider-compact\" alt=\"Livre du séjour\" />
<div class=\"slider-text-compact\">
<span class=\"slider-title-compact\" style=\"color: #f09e7a\">Pensez à commander le livre</span>
<span class=\"slider-subtitle-compact\" style=\"color: #41a2aa\">et offrez le plus beau des cadeaux !</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
{% endif %}
</section>
{% endif %}
{# Fin heroPromo #}
<!-- Section de contenu à atteindre après le scroll -->
<div
class=\"no-margin\"
id=\"scrollTarget\"
style=\"width: 100%; background: #ffffff; padding-top: 16px\"
>
<div class=\"no-margin\" id=\"scrollTarget\" style=\"width: 100%\">
<!-- Header premium 5sur5 -->
<header class=\"premium-5sur5-day-header\" style=\"padding: 5px 25px; background: #ffffff; border-bottom: 1px solid rgba(65, 162, 170, 0.1); margin-bottom: -15px;\">
<div class=\"d-flex justify-content-between align-items-center flex-wrap gap-3\">
<div class=\"flex-grow-1\">
<h5 class=\"premium-5sur5-day-title\" style=\"margin: 0 0 6px 0; font-size: 1.3rem; font-weight: 700; color: #1a1a1a;\">
{{ sejour.themSejour }}
</h5>
<p class=\"premium-5sur5-day-subtitle\" style=\"margin: 0; font-size: 0.9rem; color: #6b7280; font-weight: 500;\">
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 : <span style=\"color: #41a2aa; font-weight: 600;\">{{sejour.codeSejour}}</span>
</p>
</div>
<!-- 🎯 MEDIA TOOLBAR PREMIUM - Style 5sur5 -->
<div class=\"premium-5sur5-actions-group\" style=\"gap: 8px;\">
<!-- Tout -->
<button class=\"premium-5sur5-action-btn mtb-btn {% if attachementsCount|default(0) > 0 %}active{% endif %}\" data-filter=\"all\" title=\"Tout\">
<i class=\"bi bi-grid-3x3-gap-fill\"></i>
<span>Tout</span>
<span class=\"premium-5sur5-badge\" style=\"background: rgba(65, 162, 170, 0.1); color: #41A2AA;\">{{ attachementsCount|default(0) }}</span>
</button>
<!-- Favoris -->
<button
class=\"premium-5sur5-action-btn mtb-btn {% if nblikes|default(0) == 0 %}favoris-empty{% endif %}\"
data-filter=\"favoris\"
title=\"{% if nblikes|default(0) == 0 %}Pas de favoris pour l'instant{% else %}Mes favoris{% endif %}\"
{% if nblikes|default(0) == 0 %}disabled{% endif %}
>
<i class=\"bi bi-heart-fill\" style=\"color:{% if nblikes|default(0) == 0 %}#cbd5e1{% else %}#f56040{% endif %};\"></i>
<span>Favoris</span>
<span class=\"favoris-count\" id=\"mesFavCount\" data-bind=\"fav\" data-empty-text=\"Pas de favoris\">
{% if nblikes|default(0) == 0 %}
<span class=\"favoris-empty-text\" style=\"color: #94a3b8; font-style: italic; font-size: 11px;\">Pas de favoris</span>
{% else %}
<span class=\"premium-5sur5-badge\" style=\"background: rgba(245, 96, 64, 0.1); color: #F56040;\">{{ nblikes }}</span>
{% endif %}
</span>
</button>
<!-- Vocal -->
<button class=\"premium-5sur5-action-btn mtb-btn\" data-filter=\"audio\" title=\"Messages vocaux\">
<span class=\"icon-telephone-voicemail\" style=\"display: inline-flex; align-items: center; gap: 0;\">
<i class=\"bi bi-telephone-fill\" style=\"color:#ffd700; font-size: 14px;\"></i>
<i class=\"bi bi-voicemail\" style=\"color:#ffd700; font-size: 14px; margin-left: -4px;\"></i>
</span>
<span>Vocal</span>
<span class=\"mtb-count premium-5sur5-badge\" data-bind=\"audio\" style=\"background: rgba(255, 215, 0, 0.15); color: #ffa500;\">0</span>
</button>
</div>
</div>
</header>
<!-- Navigation par jours - Style Premium compact -->
<div class=\"section-days\" style=\"background: #f8fafb; border-radius: 0px; padding: 10px 12px; margin: 12px 0 16px 0; border: 1px solid rgba(65, 162, 170, 0.1);\">
<div class=\"date-navigation d-flex align-items-center justify-content-center\" style=\"gap: 0;\">
<div class=\"date-container d-flex\" style=\"overflow-x: auto; overflow-y: hidden; gap: 10px; padding: 4px 0; scrollbar-width: thin; scrollbar-color: #41a2aa #f0f0f0;\">
{% for x, groupAttach in listeattach %}
{% set xDate = date(x) %}
{% set finDate = date(sejour.dateFinSejour) %}
{% set todayStr = 'now'|date('Y-m-d') %}
{% set currentDateStr = xDate|date('Y-m-d') %}
{% if xDate <= finDate %}
{# États du jour : passé / courant / futur #}
{% set isPast = currentDateStr < todayStr %}
{% set isCurrent = currentDateStr == todayStr %}
{% set isFuture = currentDateStr > todayStr %}
{% set isFirstDay = groupAttach.isFirstDay == \"yes\" %}
{% set isLastDay = groupAttach.isLastDay == \"yes\" %}
{# Classes premium #}
{% set cardClasses = ['date-card', 'premium-day-card'] %}
{% if isFuture %}
{% set cardClasses = cardClasses|merge(['premium-day-future', 'notclickable']) %}
{% elseif isCurrent %}
{% set cardClasses = cardClasses|merge(['premium-day-current', 'active']) %}
{% else %}
{% set cardClasses = cardClasses|merge(['premium-day-past']) %}
{% if loop.last and not isCurrent %}
{% set cardClasses = cardClasses|merge(['active']) %}
{% endif %}
{% endif %}
<div
class=\"{{ cardClasses|join(' ') }} position-relative\"
id=\"iconedemoP{{ loop.index }}\"
{% if isFuture %}
data-tooltip=\"Ce jour n'est pas encore accessible\"
aria-disabled=\"true\"
{% else %}
data-bs-toggle=\"collapse\"
data-bs-target=\"#demP{{ loop.index }}\"
data-day-id=\"{{ currentDateStr }}\"
aria-expanded=\"false\"
aria-controls=\"demP{{ loop.index }}\"
tabindex=\"0\"
{% endif %}
>
{# Badge pour premier/dernier jour #}
{% if groupAttach.isFirstDay == \"yes\" %}
<span class=\"badge bg-success text-white position-absolute\" style=\"top: 5px; left: 5px; font-size: 0.5rem; padding: 0.2rem 0.2rem;\">
<i class=\"bi bi-play-fill me-1\"></i>
</span>
{% elseif groupAttach.isLastDay == \"yes\" %}
<span class=\"badge bg-info text-white position-absolute\" style=\"top: 5px; left: 5px; font-size: 0.5rem; padding: 0.2rem 0.2rem;\">
<i class=\"bi bi-flag-fill ms-1\"></i>
</span>
{% endif %}
<div class=\"card-content text-center\">
<span class=\"day\">
{{ xDate|date(\"D\")|replace({
\"Mon\": \"lun.\", \"Tue\": \"mar.\", \"Wed\": \"mer.\",
\"Thu\": \"jeu.\", \"Fri\": \"ven.\", \"Sat\": \"sam.\",
\"Sun\": \"dim.\"
}) }}
</span>
<span class=\"full-date\">
{{ xDate|date(\"d M Y\")|replace({
\"Jan\": \"janv.\", \"Feb\": \"fév.\", \"Mar\": \"mars\",
\"Apr\": \"avr.\", \"May\": \"mai\", \"Jun\": \"juin\",
\"Jul\": \"juil.\", \"Aug\": \"août\", \"Sep\": \"sept.\",
\"Oct\": \"oct.\", \"Nov\": \"nov.\", \"Dec\": \"déc.\"
}) }}
</span>
<ul class=\"media-list-horizontal\">
{% if groupAttach.countPhotos > 0 %}
<li><i class=\"bi bi-camera\"></i> {{ groupAttach.countPhotos }}</li>
{% endif %}
{% if groupAttach.countAudio > 0 %}
<li><i class=\"bi bi-mic-fill\"></i> {{ groupAttach.countAudio }}</li>
{% endif %}
{% if groupAttach.countVideos > 0 %}
<li><i class=\"bi bi-camera-video\"></i> {{ groupAttach.countVideos }}</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% endfor %}
</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 avec sidebar B2C -->
<div class=\"container-xxl\" style=\"padding: 0 25px; margin: 0 auto;\">
<div class=\"row g-2 col-md-12\" style=\"margin: 0;\">
<div class=\"col-lg-12 col-md-12\" style=\"padding: 0;\">
{# Galerie principale #}
<div class=\"container--gallery modern\" data-anchor=\"gallery\" style=\"padding: 0;\">
{% 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: 12px 0;\"
>
<div >
<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>
{% for description in sejour.jourdescripdate %}
{%if description.datejourphoto|date(\"m/d/Y\") !=\"\"%}{% if
description.datejourphoto|date(\"m/d/Y\") == x|date(\"m/d/Y\") %}
<p class=\"description\" style=\"margin-left:2%;width:95%;margin-top:1%;margin-bottom:1%;text-align:left\">
{{ description.description | nl2br }}
</p> {% endif %}
{%endif%}{% endfor %}
<!-- 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 %}
<section class=\"stay-empty-wrapper\">
<div class=\"stay-empty-card\">
<h2 class=\"stay-empty-title\">Aucune photo pour le moment, c'est normal.</h2>
<p class=\"stay-empty-intro\">
Le séjour démarre et l'équipe est pleinement avec les enfants : installation, découverte des lieux, premiers temps de vie de groupe.<br>
Les premières photos ou messages vocaux sont généralement publiés en fin de journée d'arrivée ou le lendemain matin.
</p>
<!-- Bloc \"Ce qui va arriver\" + illustration -->
<div class=\"stay-empty-expect\">
<div class=\"stay-empty-expect-content\">
<p class=\"stay-empty-expect-label\">Ce qui va arriver dans les prochaines heures :</p>
<ul class=\"stay-empty-list\">
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--check\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<polyline points=\"20 6 9 17 4 12\"/>
</svg>
</span>
<span>1 photo ou message « Bien arrivés »</span>
</li>
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--photos\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>
<circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/>
<polyline points=\"21 15 16 10 5 21\"/>
</svg>
</span>
<span>Quelques photos du lieu et du groupe</span>
</li>
<li>
<span class=\"stay-empty-list-icon stay-empty-list-icon--vocal\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/>
<path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>
</svg>
</span>
<span>Un message vocal dès que le programme le permet</span>
</li>
</ul>
</div>
<!-- Illustration compacte -->
<div class=\"stay-empty-illustration\">
<svg class=\"stay-empty-phone\" viewBox=\"0 0 120 200\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">
<rect x=\"0\" y=\"0\" width=\"120\" height=\"200\" rx=\"18\" fill=\"#1A1A2E\"/>
<rect x=\"6\" y=\"10\" width=\"108\" height=\"180\" rx=\"14\" fill=\"#fff\"/>
<rect x=\"12\" y=\"22\" width=\"44\" height=\"44\" rx=\"8\" fill=\"#F09E7A\" opacity=\"0.2\"/>
<rect x=\"62\" y=\"22\" width=\"46\" height=\"44\" rx=\"8\" fill=\"#41A2AA\" opacity=\"0.2\"/>
<rect x=\"12\" y=\"72\" width=\"96\" height=\"56\" rx=\"8\" fill=\"#9B8BF4\" opacity=\"0.15\"/>
<circle cx=\"34\" cy=\"44\" r=\"10\" fill=\"#F09E7A\"/>
<path d=\"M29 47l4-5 3 3 4-5 5 7H29z\" fill=\"#fff\"/>
<circle cx=\"85\" cy=\"44\" r=\"10\" fill=\"#41A2AA\"/>
<path d=\"M82 41v6M85 38v9M88 42v4\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"/>
<circle cx=\"60\" cy=\"100\" r=\"12\" fill=\"rgba(255,255,255,0.9)\"/>
<path d=\"M56 94l10 6-10 6V94z\" fill=\"#9B8BF4\"/>
<rect x=\"12\" y=\"138\" width=\"96\" height=\"44\" rx=\"8\" fill=\"#F8FAFB\"/>
<circle cx=\"34\" cy=\"160\" r=\"8\" fill=\"#E5E9F0\"/>
<circle cx=\"60\" cy=\"160\" r=\"8\" fill=\"#41A2AA\"/>
<circle cx=\"86\" cy=\"160\" r=\"8\" fill=\"#E5E9F0\"/>
<rect x=\"42\" y=\"4\" width=\"36\" height=\"5\" rx=\"2.5\" fill=\"#1A1A2E\"/>
</svg>
<div class=\"stay-empty-bubble stay-empty-bubble--arrived\">
<span class=\"stay-empty-bubble-icon\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<polyline points=\"20 6 9 17 4 12\"/>
</svg>
</span>
Bien arrivés !
</div>
<div class=\"stay-empty-bubble stay-empty-bubble--photos\">
<span class=\"stay-empty-bubble-icon\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>
<circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/>
<polyline points=\"21 15 16 10 5 21\"/>
</svg>
</span>
+12 photos
</div>
<span class=\"stay-empty-illustration-label\">Exemple de nouvelles à venir</span>
</div>
</div>
<!-- Note rassurante -->
<div class=\"stay-empty-reassurance\">
<svg class=\"stay-empty-reassurance-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/>
</svg>
<p>En cas de besoin important, l'école ou l'organisateur vous contactera directement, comme d'habitude.</p>
</div>
<!-- CTA -->
<div class=\"stay-empty-footer\">
<a href=\"{{ path('ParametresParent') }}\" class=\"stay-empty-cta\">
<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">
<path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>
<path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>
</svg>
Gérer mes notifications
</a>
</div>
</div>
</section>
<div style=\"height: 200px;\"></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>
<!-- CTA discret et premium pour rediriger vers la boutique -->
<a href=\"{{ path('boutique5sur5') }}\" class=\"premium-cta-subtle\" style=\"margin: 20px 0; padding: 16px 20px; background: rgba(65, 162, 170, 0.04); border: 1px solid rgba(65, 162, 170, 0.15); border-radius: 12px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: space-between; gap: 12px; text-decoration: none;\">
<div style=\"display: flex; align-items: center; gap: 12px; flex: 1;\">
<div style=\"width: 40px; height: 40px; background: rgba(65, 162, 170, 0.1); border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;\">
<i class=\"bi bi-gift\" style=\"font-size: 18px; color: #41A2AA;\"></i>
</div>
<div style=\"flex: 1; min-width: 0;\">
<p style=\"margin: 0; font-size: 14px; font-weight: 600; color: #1a1a1a; line-height: 1.4;\">Créer des souvenirs avec vos \${favoriteCount} photos</p>
<p style=\"margin: 4px 0 0 0; font-size: 12px; color: #6b7280; line-height: 1.4;\">Albums, tirages et livres personnalisés</p>
</div>
</div>
<i class=\"bi bi-chevron-right\" style=\"font-size: 18px; color: #41A2AA; flex-shrink: 0;\"></i>
</a>
</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');
// ✅ IMPORTANT : Le bouton favoris reste TOUJOURS accessible, peu importe la vue
switch (currentView) {
case 'favorites':
// Dans les favoris : bouton actif marqué visuellement
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 reste actif et accessible ✅
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;
case 'days':
default:
// Vue normale : bouton normal et accessible
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 photo-item\" data-id=\"\${item.id}\" style=\"position: relative; width: 100%; aspect-ratio: 1; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); background: #f0f0f0;\">
\${item.type === 'image' ?
`<img src=\"\${item.url}\" alt=\"Photo favorite\" loading=\"lazy\" style=\"width: 100%; height: 100%; object-fit: cover; display: block;\">` :
item.type === 'video' ?
`<video src=\"\${item.url}\" poster=\"\${item.thumbnail}\" style=\"width: 100%; height: 100%; object-fit: cover; display: block;\"></video>
<div class=\"video-overlay\" style=\"position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.3);\"><i class=\"bi bi-play-circle\" style=\"font-size: 48px; color: white;\"></i></div>` :
`<div class=\"audio-item\" style=\"width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, rgba(65, 162, 170, 0.1) 0%, rgba(245, 96, 64, 0.1) 100%);\">
<i class=\"bi bi-mic-fill\" style=\"font-size: 32px; color: #41A2AA; margin-bottom: 8px;\"></i>
<span style=\"font-size: 14px; color: #6b7280; font-weight: 600;\">Audio \${item.duration || '00:00'}</span>
</div>`
}
<!-- Actions overlay -->
<div class=\"favorite-actions\" style=\"position: absolute; top: 8px; right: 8px; display: flex; gap: 6px; opacity: 0; transition: opacity 0.3s ease;\">
<button class=\"btn-view-favorite\" onclick=\"viewFavorite('\${item.id}')\" title=\"Voir en grand\" style=\"width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.9); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">
<i class=\"bi bi-eye\" style=\"font-size: 14px; color: #41A2AA;\"></i>
</button>
<button class=\"btn-remove-favorite\" onclick=\"removeFavorite('\${item.id}')\" title=\"Retirer des favoris\" style=\"width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.9); border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">
<i class=\"bi bi-heart-fill\" style=\"font-size: 14px; color: #f56040;\"></i>
</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)
// 🗑️ SUPPRIMÉ : generateProductSuggestions() - remplacé par le sidebar e-commerce
// Le sidebar e-commerce (sidebar-ecommerce-pro.js) gère maintenant tous les produits
// de manière centralisée et dynamique
// Fonction pour obtenir le nombre actuel de favoris
function getCurrentFavoriteCount() {
return getFavoriteItems().length;
}
// 🎯 SUPPRIMÉ: Fonction dupliquée - la vraie est dans parent-toasts.js
// Cette fonction était en conflit avec updateProductSuggestions() de parent-toasts.js
// qui gère déjà les mises à jour dynamiques avec dates et contexte
// Fonction legacy pour compatibilité (redirige vers parent-toasts.js)
window.updateProductSuggestionsLive = function(favoriteCount) {
console.log('[Legacy] updateProductSuggestionsLive appelé, redirection vers parent-toasts.js');
// La vraie logique est dans parent-toasts.js > updateProductSuggestions()
// qui est appelée automatiquement par le MutationObserver
}
// 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 mesFavCount = document.getElementById('mesFavCount');
const count = mesFavCount ? getFavorisCount() : 0;
counter.textContent = count;
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;
// 🎯 CAS SPÉCIAL : Bouton favoris (filtre + ouvre sidebar, JAMAIS de modification du compteur)
if (filter === 'favoris') {
console.log('💗 BOUTON FAVORIS CLIQUÉ - Vue favoris + ouverture sidebar');
// 🛡️ Protection ABSOLUE : empêcher TOUTE propagation vers d'autres handlers
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
// 1️⃣ Activer visuellement le bouton favoris
const mtbBtns = document.querySelectorAll('.mtb-btn[data-filter]');
mtbBtns.forEach(b => {
if (b === btn) {
b.classList.add('active');
} else {
b.classList.remove('active');
}
});
// 2️⃣ Filtrer la galerie pour afficher uniquement les favoris
if (typeof window.filterContent === 'function') {
window.filterContent('favoris');
} else {
console.error('❌ window.filterContent non disponible');
}
// 3️⃣ Ouvrir le sidebar e-commerce via le bouton cadeau
const giftButton = document.getElementById('gift-button-trigger');
if (giftButton) {
console.log('🎁 Ouverture du sidebar via bouton cadeau');
setTimeout(() => giftButton.click(), 100); // Petit délai pour éviter les conflits
} else {
console.warn('⚠️ Bouton cadeau non trouvé');
}
return; // ⛔ SORTIE IMMÉDIATE - pas de traitement supplémentaire
}
// 🎯 CAS SPÉCIAL : Bouton audio avec 0 message → afficher popover
if (filter === 'audio') {
const audioCount = btn.querySelector('.mtb-count[data-bind=\"audio\"]');
const count = audioCount ? parseInt(audioCount.textContent.trim()) : 0;
if (count === 0) {
console.log('🎤 Bouton audio cliqué avec 0 message - affichage popover');
e.stopPropagation();
e.preventDefault();
// Afficher le popover premium
showAudioEmptyPopover(btn);
return; // ⛔ Ne pas filtrer si 0 message
}
}
// 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);
};
// 🛡️ PROTECTION ABSOLUE : Empêcher TOUTE interaction directe avec le compteur favoris
document.addEventListener('DOMContentLoaded', () => {
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
// Bloquer tous les clics directs sur le compteur
mesFavCount.addEventListener('click', (e) => {
console.log('⚠️ Clic sur mesFavCount bloqué');
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
return false;
}, true); // Capture phase pour intercepter avant tout autre listener
// Empêcher le double-clic
mesFavCount.addEventListener('dblclick', (e) => {
console.log('⚠️ Double-clic sur mesFavCount bloqué');
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
return false;
}, true);
// Ajouter un style pour indiquer visuellement que c'est non-cliquable
mesFavCount.style.pointerEvents = 'none';
mesFavCount.style.userSelect = 'none';
console.log('🛡️ Protection mesFavCount activée - compteur non-cliquable');
}
// 🎤 Initialiser l'état du bouton audio (griser si 0 message)
initAudioButtonState();
});
// 🎤 Fonction pour afficher le popover \"Pas de message audio\"
function showAudioEmptyPopover(btn) {
// Supprimer les popovers existants
const existingPopover = document.querySelector('.audio-empty-popover');
if (existingPopover) {
existingPopover.remove();
}
// Créer le popover
const popover = document.createElement('div');
popover.className = 'audio-empty-popover';
popover.innerHTML = `
<div class=\"audio-popover-content\">
<div class=\"audio-popover-icon\">
<i class=\"bi bi-mic-mute-fill\"></i>
</div>
<div class=\"audio-popover-text\">
<strong>Aucun message audio</strong>
<p>Pas de message audio soumis pour l'instant</p>
</div>
</div>
`;
// Positionner le popover
document.body.appendChild(popover);
const btnRect = btn.getBoundingClientRect();
popover.style.position = 'fixed';
popover.style.top = `\${btnRect.bottom + 10}px`;
popover.style.left = `\${btnRect.left + (btnRect.width / 2)}px`;
popover.style.transform = 'translateX(-50%)';
// Animation d'entrée
setTimeout(() => popover.classList.add('show'), 10);
// Auto-fermeture après 3 secondes
setTimeout(() => {
popover.classList.remove('show');
setTimeout(() => popover.remove(), 300);
}, 3000);
// Fermer au clic ailleurs
const closeOnClick = (e) => {
if (!popover.contains(e.target) && !btn.contains(e.target)) {
popover.classList.remove('show');
setTimeout(() => popover.remove(), 300);
document.removeEventListener('click', closeOnClick);
}
};
setTimeout(() => document.addEventListener('click', closeOnClick), 100);
}
// 🎤 Fonction pour initialiser l'état du bouton audio
function initAudioButtonState() {
const audioBtn = document.querySelector('.mtb-btn[data-filter=\"audio\"]');
if (!audioBtn) return;
const audioCount = audioBtn.querySelector('.mtb-count[data-bind=\"audio\"]');
if (!audioCount) return;
const count = parseInt(audioCount.textContent.trim()) || 0;
if (count === 0) {
// Masquer le compteur \"0\"
audioCount.style.display = 'none';
// Griser l'icône
audioBtn.classList.add('disabled-audio');
audioBtn.style.opacity = '0.4';
audioBtn.style.cursor = 'not-allowed';
const icons = audioBtn.querySelectorAll('i');
icons.forEach(icon => {
icon.style.color = '#999 !important';
icon.style.filter = 'grayscale(100%)';
});
console.log('🎤 Bouton audio désactivé (0 message)');
} else {
// Afficher le compteur
audioCount.style.display = 'inline';
// Activer l'icône
audioBtn.classList.remove('disabled-audio');
audioBtn.style.opacity = '1';
audioBtn.style.cursor = 'pointer';
console.log(`🎤 Bouton audio activé (\${count} message\${count > 1 ? 's' : ''})`);
}
}
// 🎤 Observer les changements du compteur audio pour mettre à jour l'état
const audioCountObserver = new MutationObserver(() => {
initAudioButtonState();
});
// Démarrer l'observation quand le DOM est prêt
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
const audioCount = document.querySelector('.mtb-count[data-bind=\"audio\"]');
if (audioCount) {
audioCountObserver.observe(audioCount, {
childList: true,
characterData: true,
subtree: true
});
}
});
} else {
const audioCount = document.querySelector('.mtb-count[data-bind=\"audio\"]');
if (audioCount) {
audioCountObserver.observe(audioCount, {
childList: true,
characterData: true,
subtree: true
});
}
}
// 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();
}
// Fonction helper pour lire le compteur de favoris (gère les badges premium)
function getFavorisCount() {
const mesFavCount = document.getElementById('mesFavCount');
if (!mesFavCount) return 0;
const badge = mesFavCount.querySelector('.premium-5sur5-badge');
if (badge) {
return parseInt(badge.textContent.trim() || '0');
}
const countText = mesFavCount.textContent.trim();
if (countText === 'Pas de favoris' || countText.includes('Pas de favoris')) {
return 0;
}
return parseInt(countText || '0');
}
// Fonction helper pour mettre à jour l'affichage du compteur de favoris
function updateFavorisDisplay(count) {
const mesFavCount = document.getElementById('mesFavCount');
const favorisBtn = document.querySelector('.mtb-btn[data-filter=\"favoris\"]');
const favorisIcon = favorisBtn?.querySelector('i.bi-heart-fill');
if (!mesFavCount || !favorisBtn) return;
if (count === 0) {
// Afficher \"Pas de favoris\" au lieu de \"0\" avec style premium
mesFavCount.innerHTML = '<span class=\"favoris-empty-text\" style=\"color: #94a3b8; font-style: italic; font-size: 11px;\">Pas de favoris</span>';
favorisBtn.classList.add('favoris-empty');
favorisBtn.disabled = true;
favorisBtn.title = \"Pas de favoris pour l'instant\";
if (favorisIcon) {
favorisIcon.style.color = '#cbd5e1';
}
} else {
// Afficher le badge premium avec le nombre
mesFavCount.innerHTML = `<span class=\"premium-5sur5-badge\" style=\"background: rgba(245, 96, 64, 0.1); color: #F56040; font-size: 10px; padding: 2px 6px;\">\${count}</span>`;
favorisBtn.classList.remove('favoris-empty');
favorisBtn.disabled = false;
favorisBtn.title = \"Mes favoris\";
if (favorisIcon) {
favorisIcon.style.color = '#f56040';
}
}
}
function supprimerFavoris(\$id, \$idSejour) {
// 🚨 LOG CRITIQUE : Tracer TOUS les appels à supprimerFavoris
console.error('🚨🚨🚨 supprimerFavoris() APPELÉ ! 🚨🚨🚨', {
id: \$id,
idSejour: \$idSejour,
stack: new Error().stack,
mesFavCountAvant: document.getElementById('mesFavCount')?.textContent
});
// 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 UNIQUEMENT #mesFavCount (source unique de vérité)
// L'observer se chargera de synchroniser giftCount automatiquement
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
let currentCount = getFavorisCount();
currentCount = Math.max(0, currentCount - 1); // Empêche d'aller en dessous de 0
updateFavorisDisplay(currentCount);
console.log('[supprimerFavoris] ✅ mesFavCount décrémenté:', currentCount);
// L'observer MutationObserver va automatiquement :
// - Synchroniser giftCount
// - Mettre à jour le sidebar
// - Mettre à jour product-suggestions
}
// 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) {
// 🟢 LOG : Tracer les ajouts de favoris
console.log('🟢 AddFavoris() APPELÉ', {
id: \$id,
idSejour: \$idSejour,
mesFavCountAvant: document.getElementById('mesFavCount')?.textContent
});
// 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>\");
// METTRE À JOUR UNIQUEMENT #mesFavCount (source unique de vérité)
// L'observer se chargera de synchroniser giftCount automatiquement
const mesFavCount = document.getElementById('mesFavCount');
if (mesFavCount) {
let currentCount = getFavorisCount();
currentCount++;
updateFavorisDisplay(currentCount);
console.log('[AddFavoris] ✅ mesFavCount incrémenté:', currentCount);
// L'observer MutationObserver va automatiquement :
// - Synchroniser giftCount
// - Mettre à jour le sidebar
// - Mettre à jour product-suggestions
}
// 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 (e) {
const heartIcon = \$(this);
// 🛡️ PROTECTION : Ignorer les clics sur le bouton favoris de la toolbar
// Ce bouton sert uniquement à FILTRER, pas à ajouter/supprimer des favoris
if (heartIcon.closest('.mtb-btn').length > 0) {
console.log('💗 Clic sur bouton toolbar favoris ignoré (gestion séparée)');
return; // Ne rien faire, c'est géré par le listener de la toolbar
}
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();
// 🎯 Product suggestions sont mis à jour automatiquement par le MutationObserver
// dans parent-toasts.js qui observe #mesFavCount
// updateProductSuggestionsLive(currentCount); // ← SUPPRIMÉ (doublon)
}
}, 50);
});
// Ajoutez les événements sur les icônes de cœur
document.querySelectorAll('.IconDelete').forEach((icon) => {
icon.addEventListener('click', (event) => {
const isFavorite = icon && icon.classList && icon.classList.contains('bi-heart-fill');
if (isFavorite) {
removeFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart-fill');
icon.classList.add('bi-heart');
}
} else {
addFavorite();
if (icon.classList) {
icon.classList.remove('bi-heart');
icon.classList.add('bi-heart-fill');
}
}
});
});
// Vérifie l'état initial
checkFavoritesAlert();
// ⚡ OPTIMISÉ: Réduction du délai d'initialisation
</script>
<!-- Initialisation -->
<script>
// ⚡ OPTIMISATION: Différer l'initialisation d'AOS pour ne pas bloquer le chargement
setTimeout(function() {
AOS.init({
duration: 800,
easing: \"ease-in-out\"
});
}, 100);
// 🎯 DAY FILTER DROPDOWN LOGIC (Senior UX)
document.addEventListener('DOMContentLoaded', function() {
initializeDayFilters();
// Initialiser le compteur audio avec la valeur correcte
setTimeout(function() {
updateAudioButtonState('days');
}, 500);
});
function initializeDayFilters() {
const dropdowns = document.querySelectorAll('.day-filter-dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('.filter-toggle');
const menu = dropdown.querySelector('.filter-dropdown-menu');
const options = dropdown.querySelectorAll('.filter-option');
const dayIndex = dropdown.dataset.dayIndex;
const dayContainer = document.getElementById(`demP\${dayIndex}`);
if (!toggle || !menu || !dayContainer) return;
// Calculer et mettre à jour les compteurs initiaux
updateDayFilterCounts(dropdown, dayContainer);
// Toggle dropdown
toggle.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = dropdown.classList.contains('open');
// Fermer tous les autres dropdowns
document.querySelectorAll('.day-filter-dropdown.open').forEach(d => {
if (d !== dropdown) d.classList.remove('open');
});
// Toggle le dropdown actuel
dropdown.classList.toggle('open', !isOpen);
});
// Gérer les clics sur les options
options.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const filter = option.dataset.filter;
// Mettre à jour l'état actif
options.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
// Appliquer le filtre
applyDayFilter(dayContainer, filter);
// Fermer le dropdown
dropdown.classList.remove('open');
});
});
});
// Fermer les dropdowns en cliquant ailleurs
document.addEventListener('click', () => {
document.querySelectorAll('.day-filter-dropdown.open').forEach(dropdown => {
dropdown.classList.remove('open');
});
});
}
function updateDayFilterCounts(dropdown, dayContainer) {
const photoItems = dayContainer.querySelectorAll('[data-type=\"photo\"]');
const videoItems = dayContainer.querySelectorAll('[data-type=\"video\"]');
// Compter les messages audio individuels plutôt que les conteneurs
const audioMessageItems = dayContainer.querySelectorAll('.audio-message-item[data-type=\"audio\"]');
const audioContainers = dayContainer.querySelectorAll('.audio-messages-container[data-type=\"audio\"]');
const audioRestricted = dayContainer.querySelectorAll('.audio-messages-restricted[data-type=\"audio\"]');
const allItems = dayContainer.querySelectorAll('[data-type]');
const countAll = dropdown.querySelector('[data-count-all]');
const countPhoto = dropdown.querySelector('[data-count-photo]');
const countVideo = dropdown.querySelector('[data-count-video]');
const countAudio = dropdown.querySelector('[data-count-audio]');
// Compter les messages audio individuels + conteneurs + sections restreintes
const totalAudio = audioMessageItems.length + audioContainers.length + audioRestricted.length;
if (countAll) countAll.textContent = allItems.length;
if (countPhoto) countPhoto.textContent = photoItems.length;
if (countVideo) countVideo.textContent = videoItems.length;
if (countAudio) countAudio.textContent = totalAudio;
// Masquer les options sans contenu
const photoOption = dropdown.querySelector('[data-filter=\"photo\"]');
const videoOption = dropdown.querySelector('[data-filter=\"video\"]');
const audioOption = dropdown.querySelector('[data-filter=\"audio\"]');
if (photoOption) photoOption.style.display = photoItems.length > 0 ? 'flex' : 'none';
if (videoOption) videoOption.style.display = videoItems.length > 0 ? 'flex' : 'none';
if (audioOption) audioOption.style.display = totalAudio > 0 ? 'flex' : 'none';
}
function applyDayFilter(dayContainer, filter) {
const items = dayContainer.querySelectorAll('[data-type]');
items.forEach(item => {
if (filter === 'all' || item.dataset.type === filter) {
item.style.display = '';
item.classList.remove('filtered-out');
} else {
item.style.display = 'none';
item.classList.add('filtered-out');
}
});
// Animation fluide pour les éléments visibles
requestAnimationFrame(() => {
const visibleItems = dayContainer.querySelectorAll('[data-type]:not(.filtered-out)');
visibleItems.forEach((item, index) => {
item.style.animation = `fadeInUp 0.3s ease forwards \${index * 0.05}s`;
});
});
}
// Animation CSS pour fadeInUp
if (!document.querySelector('#dayFilterAnimations')) {
const style = document.createElement('style');
style.id = 'dayFilterAnimations';
style.textContent = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
document.head.appendChild(style);
}
document.addEventListener(\"DOMContentLoaded\", function () {
const dateCards = document.querySelectorAll(\".date-card\");
const sections = document.querySelectorAll(\".collapse\");
dateCards.forEach((card) => {
card.addEventListener(\"click\", function () {
// Supprimer les classes actives des autres cartes et sections
dateCards.forEach((c) => c.classList.remove(\"active\"));
sections.forEach((s) => s.classList.remove(\"show\"));
// Ajouter la classe active à la carte cliquée
this.classList.add(\"active\");
// Récupérer la cible et afficher la bonne section
const targetId = this.getAttribute(\"data-bs-target\");
const targetSection = document.querySelector(targetId);
if (targetSection) {
targetSection.classList.add(\"show\");
}
});
});
});
document.addEventListener(\"DOMContentLoaded\", function () {
// Initialisation du carrousel Splide
var splide = new Splide(\"#imageSlider\", {
type: \"loop\",
perPage: 1,
autoplay: true,
interval: 6000,
pauseOnHover: false,
pauseOnFocus: false,
pagination: false,
arrows: false,
});
splide.mount();
// ⚠️ Scroll automatique désactivé pour meilleure UX
// Les utilisateurs peuvent défiler manuellement quand ils le souhaitent
});
</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;
}
console.log('🎁 [OPEN SIDEBAR] Ouverture du sidebar e-commerce...');
const favCount = window.getFavoriteCount ? window.getFavoriteCount() : 0;
console.log('🎁 [OPEN SIDEBAR] Favoris actuels:', favCount);
// D'abord ouvrir le sidebar pour que les éléments soient dans le DOM
sidebar.classList.add('active');
console.log('🎁 [OPEN SIDEBAR] Sidebar classe \"active\" ajoutée');
// Puis mettre à jour le contenu (petit délai pour que le DOM soit prêt)
setTimeout(() => {
console.log('🎁 [OPEN SIDEBAR] Mise à jour du contenu...');
// Appeler la fonction de parent-toasts.js
if (typeof window.parentConversion !== 'undefined' && typeof window.parentConversion.updateSidebar === 'function') {
console.log('✅ [OPEN SIDEBAR] Appel de parentConversion.updateSidebar()');
window.parentConversion.updateSidebar();
} else {
console.warn('⚠️ [OPEN SIDEBAR] parent-toasts.js non disponible, fallback sur fonction locale');
// Fallback sur la fonction locale
if (typeof window.updateEcommerceSidebarContent === 'function') {
window.updateEcommerceSidebarContent(favCount);
}
}
}, 100); // 100ms de délai pour que le sidebar soit visible
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 LEGACY SUPPRIMÉE - Utiliser parent-toasts.js à la place
// Cette fonction était en conflit avec updateEcommerceSidebar() de parent-toasts.js
// qui gère TOUT : titre, sous-titre, compteurs, visibilité des produits, CTA, etc.
window.updateEcommerceSidebarContent = function updateEcommerceSidebarContent(favoriteCount) {
console.warn('[LEGACY] updateEcommerceSidebarContent appelé - Redirection vers parent-toasts.js');
// Rediriger vers la vraie fonction
if (typeof window.parentConversion !== 'undefined' && typeof window.parentConversion.updateSidebar === 'function') {
window.parentConversion.updateSidebar();
} else {
console.error('[LEGACY] parent-toasts.js non disponible !');
}
}
// 🚫 FONCTION SUPPRIMÉE - Géré par parent-toasts.js
// Tout est maintenant géré dans updateEcommerceSidebar() de parent-toasts.js
// 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();
};
// Fonction centrale de mise à jour de TOUS les compteurs de favoris
window.updateAllFavoriteCounters = function() {
// Récupérer le compte depuis #mesFavCount (source unique de vérité)
const mesFavCount = document.getElementById('mesFavCount');
let favoriteCount = 0;
if (mesFavCount) {
favoriteCount = parseInt(mesFavCount.textContent || '0');
}
console.log('[updateAllFavoriteCounters] 💗 Synchronisation:', favoriteCount, 'favoris');
// Synchroniser giftCount (bouton cadeau) avec animation
const giftCount = document.getElementById('giftCount');
if (giftCount) {
giftCount.textContent = favoriteCount;
giftCount.classList.remove('gift-count-bounce');
setTimeout(() => giftCount.classList.add('gift-count-bounce'), 10);
}
// 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>
{# ==================== Zones B2C Suggestions ==================== #}
{# Sidebar suggestions (desktop) #}
<aside class=\"col-lg-3 d-none d-lg-block\" style=\"position:sticky;top:100px;\">
<div id=\"parentSuggestions\" class=\"pcb-sticky\">
{# Rempli dynamiquement par parent-b2c-nuke.js #}
</div>
</aside>
{# Bloc suggestions mobile #}
<div class=\"col-12 d-lg-none mt-3\">
<div id=\"parentSuggestionsMobile\">
{# Rempli dynamiquement par parent-b2c-nuke.js #}
</div>
</div>
{# ==================== SIDEBAR E-COMMERCE ==================== #}
<script src=\"{{ asset('js/sidebar-ecommerce-pro.js') }}\"></script>
{% endblock %}
", "Parent/DetailsSejour.html.twig", "/var/www/5sur5sejour/templates/Parent/DetailsSejour.html.twig");
}
}