{% set filterColor = carousel.filterColor|default('#000') %}
{% set filterColorOpacity = carousel.filterColorOpacity|default(0) %}
{# Paramètres des vignettes #}
{% set showThumbnails = carousel.showThumbnails|default(false) %}
{% set height = carousel.height|default(400) %}
{% set heightUnit = carousel.heightUnit|default('px') %}
{% set bordersRadius = carousel.bordersRadius|default(0) ~ carousel.bordersRadiusUnit|default('px') %}
{% set isSwitchSlideTime = carousel.isSwitchSlideTime|default(false) %}
{% set switchSlideTime = (isSwitchSlideTime and carousel.switchSlideTime is not null and carousel.switchSlideTime != 0 ? carousel.switchSlideTime * 1000 : 5000) %}
{% set carouselId = 'carousel-' ~ random() %}
{% set slidesToUse = slidesByType['carousel1'] ?? [] %}
{% set firstCarouselSlide = slidesToUse|first %}
{% set carouselLcpPreloadHref = null %}
{% if firstCarouselSlide %}
{% set se0 = firstCarouselSlide.elements ?? [] %}
{% set med0 = null %}
{% if firstCarouselSlide.mediaUrl is not empty %}
{% set med0 = {'url': firstCarouselSlide.mediaUrl, 'thumbnails': {'800x': firstCarouselSlide.mediaUrl, '1200x': firstCarouselSlide.mediaUrl}, 'isVideo': firstCarouselSlide.isVideo} %}
{% elseif se0.media is defined and se0.media.id is defined %}
{% set med0 = sulu_resolve_media(se0.media.id, app.request.locale) %}
{% endif %}
{% if med0 is not null %}
{% set mUrl0 = med0.url %}
{% set isVid0 = med0.isVideo is defined ? med0.isVideo : (mUrl0 matches '/\.mp4(\\?.*)?$/' or mUrl0 matches '/\.webm(\\?.*)?$/' or mUrl0 matches '/\.ogg(\\?.*)?$/') %}
{% if not isVid0 %}
{% set carouselLcpPreloadHref = sulu_media_url(med0, '1200x') %}
{% else %}
{% set poster0 = sulu_media_url(med0, '1200x') %}
{% if poster0 and not (poster0 matches '/\.(mp4|webm|ogg)(\\?.*)?$/i') %}
{% set carouselLcpPreloadHref = poster0 %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% if true %}
<div class="carousel-wrapper"
data-type="carousel"
id="{{ carouselId }}"
data-carousel-id="{{ carouselId }}"
data-carousel-options='{{ {
"showDots": carousel.showDots|default(false),
"showThumbnails": showThumbnails,
"isAutoSlide": isSwitchSlideTime,
"animation": {
"contentDuration": 0.5,
"contentDelay": 0.2,
"mediaDuration": 0.55,
"mediaDelay": 0
}
}|json_encode|raw }}'
style="--switch-slide-time: {{ switchSlideTime }}ms;">
{% if carouselLcpPreloadHref %}
<link rel="preload" as="image" href="{{ carouselLcpPreloadHref }}" fetchpriority="high">
{% endif %}
<div class="carousel-container">
<div class="carousel" data-observed>
{% for slideEntity in slidesToUse %}
{# ============================================================ #}
{# RECONSTRUCTION DE LA STRUCTURE SLIDE DEPUIS L'ENTITÉ SLIDE #}
{# ============================================================ #}
{# Extraire les données de l'entité Slide #}
{% set slideElements = slideEntity.elements ?? [] %}
{% set slideStyles = slideEntity.styles ?? {} %}
{# Gestion du média : mediaUrl de l'entité ou depuis elements #}
{% set media = null %}
{% if slideEntity.mediaUrl is not empty %}
{% set media = {'url': slideEntity.mediaUrl, 'thumbnails': {'800x': slideEntity.mediaUrl, '1200x': slideEntity.mediaUrl}, 'isVideo': slideEntity.isVideo} %}
{% elseif slideElements.media is defined and slideElements.media.id is defined %}
{% set media = sulu_resolve_media(slideElements.media.id, app.request.locale) %}
{% endif %}
{% if media is not null %}
{% set mediaUrl = sulu_media_original_url(media) %}
{% set mediaPoster = sulu_media_url(media, '1200x') %}
{% set mediaThumb800 = sulu_media_url(media, '800x') %}
{% set mediaThumb1200 = sulu_media_url(media, '1200x') %}
{% set mediaThumb1920 = sulu_media_url(media, '1600x') %}
{# Utiliser isVideo depuis la base de données si disponible, sinon deviner depuis l'extension #}
{% set isVideo = media.isVideo is defined ? media.isVideo : (mediaUrl matches '/\.mp4(\\?.*)?$/' or mediaUrl matches '/\.webm(\\?.*)?$/' or mediaUrl matches '/\.ogg(\\?.*)?$/') %}
{% else %}
{% set mediaUrl = '' %}
{% set mediaPoster = '' %}
{% set mediaThumb800 = null %}
{% set mediaThumb1200 = null %}
{% set mediaThumb1920 = null %}
{% set isVideo = false %}
{% endif %}
{# Reconstruire la structure slide complète #}
{% set slide = {
'media': media,
'title': slideEntity.subtitle ?? '',
'isColorGradient': slideStyles.isColorGradient ?? false,
'isCustomBackgroundFilter': slideStyles.isCustomBackgroundFilter ?? false,
'customBackgroundFilterColor': slideStyles.customBackgroundFilterColor ?? '#000000',
'customBackgroundFilterOpacity': slideStyles.customBackgroundFilterOpacity ?? 40,
'gradientOrientation': slideStyles.gradientOrientation ?? 'leftRight',
'color1': slideStyles.color1 ?? '#fff',
'color1Opacity': slideStyles.color1Opacity ?? 100,
'color2': slideStyles.color2 ?? '#000',
'color2Opacity': slideStyles.color2Opacity ?? 100,
'isCommonCompanySelector': slideEntity.companyId is not empty,
'CommonCompanySelector': slideEntity.companyId ?? '',
'companyCustomLogo': slideElements.companyCustomLogo ?? null,
'isDestinationSelector': false,
'destinationSelector': '',
'destinationCustomName': '',
'contents': slideElements is iterable ? slideElements : [],
'isNoCustom': false,
'isCustomDuration': false,
'customDuration': '',
'isCustomPrice': false,
'customPrice': '',
'advicedDuration': slideEntity.subtitle ?? '',
'advicedPeriod': '',
'bonusTexts': []
} %}
<div class="carousel-slide">
{# Media #}
{% if slide.isColorGradient %}
{% set gradientDirections = {
'leftRight': 'to right',
'rightLeft': 'to left',
'topBottom': 'to bottom',
'bottomTop': 'to top',
'topLeftBottomRight': 'to bottom right',
'bottomLeftTopRight': 'to top right',
'topRightBottomLeft': 'to bottom left',
'bottomRightTopLeft': 'to top left'
} %}
{% set gradientDirection = gradientDirections[slide.gradientOrientation|default('leftRight')] %}
<div class="carousel-color-gradient carousel-media"
style="background: linear-gradient({{ gradientDirection }}, {{ slide.color1|default('#000') }}, {{ slide.color2|default('#fff') }});">
</div>
{% else %}
{% if isVideo %}
<video class="carousel-media"
autoplay muted loop playsinline
preload="metadata"
poster="{{ mediaPoster }}"
{% if loop.first %}fetchpriority="high"{% endif %}>
<source src="{{ mediaUrl }}" type="video/mp4">
<p>{{ 'carousel.destination.video_not_supported'|trans }}</p>
</video>
{% else %}
{% set isCarouselLcpImage = loop.first and mediaPoster is not empty %}
<img class="carousel-media{{ isCarouselLcpImage ? ' carousel-media--lcp' : '' }}"
src="{{ mediaPoster }}"
{% if mediaThumb800 and mediaThumb1200 and mediaThumb800 != mediaThumb1200 %}
srcset="{{ mediaThumb800 }} 800w{% if mediaThumb1200 %}, {{ mediaThumb1200 }} 1200w{% endif %}{% if mediaThumb1920 %}, {{ mediaThumb1920 }} 1920w{% endif %}"
sizes="100vw"
{% elseif mediaThumb800 %}
srcset="{{ mediaThumb800 }} 800w"
sizes="100vw"
{% endif %}
alt="{{ slide.title|default('')|striptags|e('html_attr') }}"
decoding="async"
{% if isCarouselLcpImage %}
fetchpriority="high"
{% else %}
loading="lazy" fetchpriority="low"
{% endif %}>
{% endif %}
{% endif %}
{% if carousel.filterColor and not slide.isColorGradient %}
<div class="carousel-overlay"
style="background-color: {{ filterColor }};
opacity: {{ filterColorOpacity ?? 0.4 }};">
</div>
{% endif %}
<div class="carousel-content">
<div class="line">
{% if slide.isCommonCompanySelector and slide.CommonCompanySelector is not empty %}
<div class="content-company-logo">
{% if slide.companyCustomLogo is not empty and slide.companyCustomLogo.url is defined %}
<img src="{{ slide.companyCustomLogo.url }}" alt="{{ 'common.company_logo'|trans }}">
{% else %}
{% set companyLogo = get_company_logo_by_id(slide.CommonCompanySelector) %}
{% if companyLogo is not empty %}
<img src="{{ companyLogo }}" alt="{{ 'common.company_logo'|trans }}">
{% endif %}
{% endif %}
</div>
{% endif %}
{% if slide.title is defined and slide.title is not empty %}<div class="carousel-title">{{ slide.title|replace_slide_offer_variables(slideEntity.offerId)|raw }}</div>{% endif %}
{% for element in slide.contents %}
{# Type : text - Afficher un texte simple #}
{% if element.type == 'text' %}
<div class="carousel-text">{{ element.content|replace_slide_offer_variables(slideEntity.offerId)|raw }}</div>
{# Type : button - Afficher un bouton avec style comme classe CSS #}
{% elseif element.type == 'button' %}
{% set buttonText = (element.text ?? '')|replace_slide_offer_variables(slideEntity.offerId) %}
{% set buttonUrl = element.url ?? '#' %}
{% set buttonStyle = element.style ?? 'style1' %}
{% set buttonSize = element.size|default('medium') %}
{% set hasArrow = element.hasArrow|default(false) %}
{% set hasNewTab = element.hasNewTab|default(false) %}
<div class="carousel-button-wrapper">
<a href="{{ buttonUrl }}"
class="btn {{ buttonStyle }} {{ buttonSize }} reverse_color"
{% if hasNewTab %}target="_blank" rel="noopener"{% endif %}>
{{ buttonText }}
{% if hasArrow %}{{ getLucidIcon('arrow-right') }}{% endif %}
</a>
</div>
{# Type : image - Afficher une image #}
{% elseif element.type == 'image' %}
<div class="carousel-image">
<img src="{{ sulu_media_url(element.content, '1200x') }}"
alt="{{ element.alt ?? '' }}"
loading="lazy"
style="{% if element.width is defined %}width: {{ element.width }}px;{% endif %}{% if element.height is defined %} height: {{ element.height }}px;{% endif %}">
</div>
{% endif %}
{% endfor %}
{% if slide.tags is defined and slide.tags is not empty %}
<div class="content-tags">
{% for tag in slide.tags %}
<div class="content-tag">{{ tag.tag }}</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{# Navigation avec dots (style blog) #}
<div class="carousel-navigation">
<span class="carousel-nav-icon carousel-nav-icon--prev" hidden>{{ getLucidIcon('chevron-left')|raw }}</span>
<span class="carousel-nav-icon carousel-nav-icon--next" hidden>{{ getLucidIcon('chevron-right')|raw }}</span>
</div>
{# Vignettes #}
{% if showThumbnails %}
{% set inactiveFilter = carousel.thumbnailInactiveFilter|default('grayscale') %}
<div class="carousel-thumbnails" data-carousel-id="{{ carouselId }}">
{% for slideEntity in slidesToUse %}
{# Utiliser les données de l'entité Slide #}
{% set slideElements = slideEntity.elements ?? [] %}
{% set media = null %}
{% if slideEntity.mediaUrl is not empty %}
{% set media = {'url': slideEntity.mediaUrl, 'thumbnails': {'200x': slideEntity.mediaUrl, '400x': slideEntity.mediaUrl}, 'isVideo': slideEntity.isVideo} %}
{% elseif slideElements.media is defined and slideElements.media.id is defined %}
{% set media = sulu_resolve_media(slideElements.media.id, app.request.locale) %}
{% endif %}
{% if media is not null %}
{% set mediaUrl = sulu_media_original_url(media) %}
{% set isVideo = media.isVideo is defined ? media.isVideo : (mediaUrl matches '/\.mp4(\\?.*)?$/' or mediaUrl matches '/\.webm(\\?.*)?$/' or mediaUrl matches '/\.ogg(\\?.*)?$/') %}
{% else %}
{% set mediaUrl = '' %}
{% set isVideo = false %}
{% endif %}
{# Définir le nom de la destination pour cette vignette #}
{% set destinationName = '' %}
{% if isVideo %}
{# Pour les vidéos, utiliser un canvas pour capturer le premier frame #}
<div class="thumbnail {% if loop.first %}active{% endif %} filter-{{ inactiveFilter }}"
data-index="{{ loop.index0 }}"
data-carousel-id="{{ carouselId }}"
data-video-url="{{ mediaUrl }}">
<canvas class="video-thumbnail-canvas" width="{{ carousel.thumbnailWidth|default(120) }}" height="{{ carousel.thumbnailHeight|default(80) }}"></canvas>
<div class="video-play-icon">▶</div>
{% else %}
{# Pour les images, utiliser les thumbnails normaux #}
{% set mediaPoster = sulu_media_url(media, '200x') %}
<div class="thumbnail {% if loop.first %}active{% endif %} filter-{{ inactiveFilter }}"
data-index="{{ loop.index0 }}"
data-carousel-id="{{ carouselId }}">
<img src="{{ mediaPoster }}"
alt="{{ media.title|default('Thumbnail ' ~ loop.index) }}"
loading="lazy">
{% endif %}
{# Afficher le nom de la destination #}
{% if destinationName is not empty %}
<div class="thumbnail-label">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
<span>{{ destinationName }}</span>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
{# Le JavaScript du carousel est géré par snippets/hero-section.js (même système que hero_section) #}
{# La navigation avec dots est gérée par snippets/carousel.js #}
<style>
#{{ carouselId }} {
/* Général */
--borders-radius: {{ carousel.bordersRadius|default(0) ~ carousel.bordersRadiusUnit|default('px') }};
--text-width: {{ carousel.textWidth|default(100) ~ carousel.textWidthUnit|default('%') }};
--height: {{ carousel.height|default(400) ~ carousel.heightUnit|default('px') }};
--gap-elements: {{ carousel.gapElements|default(12) ~ carousel.gapElementsUnit|default('px') }};
--carousel-padding: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') ~ ' ' ~ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
--carousel-padding-top: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') }};
--carousel-padding-bottom: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') }};
--carousel-padding-left: {{ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
--carousel-padding-right: {{ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
/* Vignettes */
--thumbnails-gap: {{ carousel.thumbnailGap|default(10) }}px;
--thumbnails-width: {{ carousel.thumbnailWidth|default(120) }}px;
--thumbnails-height: {{ carousel.thumbnailHeight|default(80) }}px;
--thumbnails-border-radius: {{ carousel.thumbnailBorderRadius|default(8) }}px;
--thumbnail-active-border: {{ carousel.thumbnailActiveBorderWidth|default(3) ~ 'px solid ' ~ carousel.thumbnailActiveBorderColor|default('#8B5CF6') }};
--thumbnail-border: {{ carousel.thumbnailActiveBorderWidth|default(3) ~ 'px solid transparent' }};
/* Vignettes responsive */
--responsive-thumbnails-gap: {{ carousel.thumbnailGap|default(10) * 0.8 }}px;
--responsive-thumbnails-width: {{ carousel.thumbnailWidth|default(120) * 0.9 }}px;
--responsive-thumbnails-height: {{ carousel.thumbnailHeight|default(80) * 0.9 }}px;
/* Filtres des vignettes inactives */
--thumbnail-filter-intensity: {{ carousel.thumbnailInactiveFilterIntensity|default(50) }};
--thumbnail-overlay-color: {{ carousel.thumbnailInactiveOverlayColor|default('#000000') }};
/* Elements textuels des vignettes */
--thumbnail-text-color: {{ carousel.thumbnailTextColor|default('#fff') }};
--thumbnail-text-size: {{ carousel.thumbnailTextSize|default(12) ~ carousel.thumbnailTextSizeUnit|default('px') }};
--thumbnail-text-weight: {{ carousel.thumbnailTextWeight|default(600) }};
--thumbnail-text-align: {{ carousel.thumbnailTextAlign|default('left') }};
--thumbnail-text-border-radius: {{ carousel.thumbnailTextBorderRadius|default(8) ~ carousel.thumbnailTextBorderRadiusUnit|default('px') }};
/* Fond des étiquettes via ::after */
--thumbnail-label-background: {{ carousel.thumbnailLabelBackground|default(carousel.thumbnailTextBackground|default('#000000')) }};
--thumbnail-label-background-opacity: {{ carousel.thumbnailLabelBackgroundOpacity|default(carousel.thumbnailTextBackgroundOpacity|default(70)) }};
--thumbnail-label-border-radius: {{ carousel.thumbnailTextBorderRadius|default(20) ~ carousel.thumbnailTextBorderRadiusUnit|default('px') }};
/* Bouton */
--main-color-button: {{ carousel.mainColorButton|default('#004461') }};
--secondary-color-button: {{ carousel.secondaryColorButton|default('#fff') }};
--borders-radius-button: {{ carousel.bordersRadiusButton|default(8) ~ carousel.bordersRadiusButtonUnit|default('px') }};
--padding-button: {{ carousel.paddingSizeTopBottomButton|default(4) ~ carousel.paddingButtonUnit|default('px') ~ ' ' ~ carousel.paddingSizeLeftRightButton|default(4) ~ carousel.paddingButtonUnit|default('px') }};
/* Bouton dynamique */
--dynamic-button-text-color: {{ carousel.dynamicButtonTextColor|default('#fff') }};
--dynamic-button-background: {{ carousel.dynamicButtonBackground|default('#004461') }};
--dynamic-button-price-color: {{ carousel.dynamicButtonPriceColor|default('#000') }};
--dynamic-button-border-radius: {{ carousel.dynamicButtonBorderRadius|default(8) ~ carousel.dynamicButtonBorderRadiusUnit|default('px') }};
--dynamic-button-padding: {{ carousel.dynamicButtonPaddingTopBottom|default(4) ~ carousel.dynamicButtonPaddingTopBottomUnit|default('px') ~ ' ' ~ carousel.dynamicButtonPaddingRightLeft|default(4) ~ carousel.dynamicButtonPaddingRightLeftUnit|default('px') }};
--dynamic-button-border: {{ carousel.dynamicButtonBorderSize|default(1) ~ carousel.dynamicButtonBorderSizeUnit|default('px') ~ ' solid ' ~ carousel.dynamicButtonBorderColor|default('#fff') }};
/* Tags */
--tags-color: {{ carousel.tagsColor|default('#000') }};
--tags-font-weight: {{ carousel.tagsFontWeight|default(600) }};
--tags-padding: {{ carousel.tagsPaddingTopBottom|default(3) ~ carousel.tagsPaddingTopBottomUnit|default('px') }} {{ carousel.tagsPaddingRightLeft|default(6) ~ carousel.tagsPaddingRightLeftUnit|default('px') }};
--tags-border-radius: {{ carousel.tagsBorderRadius|default(12) ~ carousel.tagsBorderRadiusUnit|default('px') }};
--tags-font-size: {{ carousel.tagsFontSize|default(10) ~ carousel.tagsFontSizeUnit|default('px') }};
--tags-background-color: {{ carousel.tagsBackgroundColor|default('rgba(217, 217, 217, 0.20)') }};
/* Dots */
--dot-color-1: {{ carousel.dotColor1|default('#004461') }};
--dot-color-2: {{ carousel.dotColor2|default('#00AAD7') }};
{% set logoWidthUnit = carousel.logoWidth is defined and carousel.logoWidth is not empty or carousel.logoHeight is defined and carousel.logoHeight is not empty ? carousel.logoUnit|default('px') : '' %}
{% set logoHeightUnit = carousel.logoHeight is defined and carousel.logoHeight is not empty ? carousel.logoUnit|default('px') : '' %}
/* Dimensions logo compagnie */
--logo-width: {{ carousel.logoWidth|default('auto') ~ logoWidthUnit }};
--logo-height: {{ carousel.logoHeight|default('auto') ~ logoHeightUnit }};
}
</style>