templates/_snippets/carousel.html.twig line 1

Open in your IDE?
  1. {% set filterColor = carousel.filterColor|default('#000') %}
  2. {% set filterColorOpacity = carousel.filterColorOpacity|default(0) %}
  3. {# Paramètres des vignettes #}
  4. {% set showThumbnails = carousel.showThumbnails|default(false) %}
  5. {% set height = carousel.height|default(400) %}
  6. {% set heightUnit = carousel.heightUnit|default('px') %}
  7. {% set bordersRadius = carousel.bordersRadius|default(0) ~ carousel.bordersRadiusUnit|default('px') %}
  8. {% set isSwitchSlideTime = carousel.isSwitchSlideTime|default(false) %}
  9. {% set switchSlideTime = (isSwitchSlideTime and carousel.switchSlideTime is not null and carousel.switchSlideTime != 0 ? carousel.switchSlideTime * 1000 : 5000) %}
  10. {% set carouselId = 'carousel-' ~ random() %}
  11. {% set slidesToUse = slidesByType['carousel1'] ?? [] %}
  12. {% set firstCarouselSlide = slidesToUse|first %}
  13. {% set carouselLcpPreloadHref = null %}
  14. {% if firstCarouselSlide %}
  15. {% set se0 = firstCarouselSlide.elements ?? [] %}
  16. {% set med0 = null %}
  17. {% if firstCarouselSlide.mediaUrl is not empty %}
  18. {% set med0 = {'url': firstCarouselSlide.mediaUrl, 'thumbnails': {'800x': firstCarouselSlide.mediaUrl, '1200x': firstCarouselSlide.mediaUrl}, 'isVideo': firstCarouselSlide.isVideo} %}
  19. {% elseif se0.media is defined and se0.media.id is defined %}
  20. {% set med0 = sulu_resolve_media(se0.media.id, app.request.locale) %}
  21. {% endif %}
  22. {% if med0 is not null %}
  23. {% set mUrl0 = med0.url %}
  24. {% set isVid0 = med0.isVideo is defined ? med0.isVideo : (mUrl0 matches '/\.mp4(\\?.*)?$/' or mUrl0 matches '/\.webm(\\?.*)?$/' or mUrl0 matches '/\.ogg(\\?.*)?$/') %}
  25. {% if not isVid0 %}
  26. {% set carouselLcpPreloadHref = sulu_media_url(med0, '1200x') %}
  27. {% else %}
  28. {% set poster0 = sulu_media_url(med0, '1200x') %}
  29. {% if poster0 and not (poster0 matches '/\.(mp4|webm|ogg)(\\?.*)?$/i') %}
  30. {% set carouselLcpPreloadHref = poster0 %}
  31. {% endif %}
  32. {% endif %}
  33. {% endif %}
  34. {% endif %}
  35. {% if true %}
  36. <div class="carousel-wrapper"
  37. data-type="carousel"
  38. id="{{ carouselId }}"
  39. data-carousel-id="{{ carouselId }}"
  40. data-carousel-options='{{ {
  41. "showDots": carousel.showDots|default(false),
  42. "showThumbnails": showThumbnails,
  43. "isAutoSlide": isSwitchSlideTime,
  44. "animation": {
  45. "contentDuration": 0.5,
  46. "contentDelay": 0.2,
  47. "mediaDuration": 0.55,
  48. "mediaDelay": 0
  49. }
  50. }|json_encode|raw }}'
  51. style="--switch-slide-time: {{ switchSlideTime }}ms;">
  52. {% if carouselLcpPreloadHref %}
  53. <link rel="preload" as="image" href="{{ carouselLcpPreloadHref }}" fetchpriority="high">
  54. {% endif %}
  55. <div class="carousel-container">
  56. <div class="carousel" data-observed>
  57. {% for slideEntity in slidesToUse %}
  58. {# ============================================================ #}
  59. {# RECONSTRUCTION DE LA STRUCTURE SLIDE DEPUIS L'ENTITÉ SLIDE #}
  60. {# ============================================================ #}
  61. {# Extraire les données de l'entité Slide #}
  62. {% set slideElements = slideEntity.elements ?? [] %}
  63. {% set slideStyles = slideEntity.styles ?? {} %}
  64. {# Gestion du média : mediaUrl de l'entité ou depuis elements #}
  65. {% set media = null %}
  66. {% if slideEntity.mediaUrl is not empty %}
  67. {% set media = {'url': slideEntity.mediaUrl, 'thumbnails': {'800x': slideEntity.mediaUrl, '1200x': slideEntity.mediaUrl}, 'isVideo': slideEntity.isVideo} %}
  68. {% elseif slideElements.media is defined and slideElements.media.id is defined %}
  69. {% set media = sulu_resolve_media(slideElements.media.id, app.request.locale) %}
  70. {% endif %}
  71. {% if media is not null %}
  72. {% set mediaUrl = sulu_media_original_url(media) %}
  73. {% set mediaPoster = sulu_media_url(media, '1200x') %}
  74. {% set mediaThumb800 = sulu_media_url(media, '800x') %}
  75. {% set mediaThumb1200 = sulu_media_url(media, '1200x') %}
  76. {% set mediaThumb1920 = sulu_media_url(media, '1600x') %}
  77. {# Utiliser isVideo depuis la base de données si disponible, sinon deviner depuis l'extension #}
  78. {% set isVideo = media.isVideo is defined ? media.isVideo : (mediaUrl matches '/\.mp4(\\?.*)?$/' or mediaUrl matches '/\.webm(\\?.*)?$/' or mediaUrl matches '/\.ogg(\\?.*)?$/') %}
  79. {% else %}
  80. {% set mediaUrl = '' %}
  81. {% set mediaPoster = '' %}
  82. {% set mediaThumb800 = null %}
  83. {% set mediaThumb1200 = null %}
  84. {% set mediaThumb1920 = null %}
  85. {% set isVideo = false %}
  86. {% endif %}
  87. {# Reconstruire la structure slide complète #}
  88. {% set slide = {
  89. 'media': media,
  90. 'title': slideEntity.subtitle ?? '',
  91. 'isColorGradient': slideStyles.isColorGradient ?? false,
  92. 'isCustomBackgroundFilter': slideStyles.isCustomBackgroundFilter ?? false,
  93. 'customBackgroundFilterColor': slideStyles.customBackgroundFilterColor ?? '#000000',
  94. 'customBackgroundFilterOpacity': slideStyles.customBackgroundFilterOpacity ?? 40,
  95. 'gradientOrientation': slideStyles.gradientOrientation ?? 'leftRight',
  96. 'color1': slideStyles.color1 ?? '#fff',
  97. 'color1Opacity': slideStyles.color1Opacity ?? 100,
  98. 'color2': slideStyles.color2 ?? '#000',
  99. 'color2Opacity': slideStyles.color2Opacity ?? 100,
  100. 'isCommonCompanySelector': slideEntity.companyId is not empty,
  101. 'CommonCompanySelector': slideEntity.companyId ?? '',
  102. 'companyCustomLogo': slideElements.companyCustomLogo ?? null,
  103. 'isDestinationSelector': false,
  104. 'destinationSelector': '',
  105. 'destinationCustomName': '',
  106. 'contents': slideElements is iterable ? slideElements : [],
  107. 'isNoCustom': false,
  108. 'isCustomDuration': false,
  109. 'customDuration': '',
  110. 'isCustomPrice': false,
  111. 'customPrice': '',
  112. 'advicedDuration': slideEntity.subtitle ?? '',
  113. 'advicedPeriod': '',
  114. 'bonusTexts': []
  115. } %}
  116. <div class="carousel-slide">
  117. {# Media #}
  118. {% if slide.isColorGradient %}
  119. {% set gradientDirections = {
  120. 'leftRight': 'to right',
  121. 'rightLeft': 'to left',
  122. 'topBottom': 'to bottom',
  123. 'bottomTop': 'to top',
  124. 'topLeftBottomRight': 'to bottom right',
  125. 'bottomLeftTopRight': 'to top right',
  126. 'topRightBottomLeft': 'to bottom left',
  127. 'bottomRightTopLeft': 'to top left'
  128. } %}
  129. {% set gradientDirection = gradientDirections[slide.gradientOrientation|default('leftRight')] %}
  130. <div class="carousel-color-gradient carousel-media"
  131. style="background: linear-gradient({{ gradientDirection }}, {{ slide.color1|default('#000') }}, {{ slide.color2|default('#fff') }});">
  132. </div>
  133. {% else %}
  134. {% if isVideo %}
  135. <video class="carousel-media"
  136. autoplay muted loop playsinline
  137. preload="metadata"
  138. poster="{{ mediaPoster }}"
  139. {% if loop.first %}fetchpriority="high"{% endif %}>
  140. <source src="{{ mediaUrl }}" type="video/mp4">
  141. <p>{{ 'carousel.destination.video_not_supported'|trans }}</p>
  142. </video>
  143. {% else %}
  144. {% set isCarouselLcpImage = loop.first and mediaPoster is not empty %}
  145. <img class="carousel-media{{ isCarouselLcpImage ? ' carousel-media--lcp' : '' }}"
  146. src="{{ mediaPoster }}"
  147. {% if mediaThumb800 and mediaThumb1200 and mediaThumb800 != mediaThumb1200 %}
  148. srcset="{{ mediaThumb800 }} 800w{% if mediaThumb1200 %}, {{ mediaThumb1200 }} 1200w{% endif %}{% if mediaThumb1920 %}, {{ mediaThumb1920 }} 1920w{% endif %}"
  149. sizes="100vw"
  150. {% elseif mediaThumb800 %}
  151. srcset="{{ mediaThumb800 }} 800w"
  152. sizes="100vw"
  153. {% endif %}
  154. alt="{{ slide.title|default('')|striptags|e('html_attr') }}"
  155. decoding="async"
  156. {% if isCarouselLcpImage %}
  157. fetchpriority="high"
  158. {% else %}
  159. loading="lazy" fetchpriority="low"
  160. {% endif %}>
  161. {% endif %}
  162. {% endif %}
  163. {% if carousel.filterColor and not slide.isColorGradient %}
  164. <div class="carousel-overlay"
  165. style="background-color: {{ filterColor }};
  166. opacity: {{ filterColorOpacity ?? 0.4 }};">
  167. </div>
  168. {% endif %}
  169. <div class="carousel-content">
  170. <div class="line">
  171. {% if slide.isCommonCompanySelector and slide.CommonCompanySelector is not empty %}
  172. <div class="content-company-logo">
  173. {% if slide.companyCustomLogo is not empty and slide.companyCustomLogo.url is defined %}
  174. <img src="{{ slide.companyCustomLogo.url }}" alt="{{ 'common.company_logo'|trans }}">
  175. {% else %}
  176. {% set companyLogo = get_company_logo_by_id(slide.CommonCompanySelector) %}
  177. {% if companyLogo is not empty %}
  178. <img src="{{ companyLogo }}" alt="{{ 'common.company_logo'|trans }}">
  179. {% endif %}
  180. {% endif %}
  181. </div>
  182. {% endif %}
  183. {% 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 %}
  184. {% for element in slide.contents %}
  185. {# Type : text - Afficher un texte simple #}
  186. {% if element.type == 'text' %}
  187. <div class="carousel-text">{{ element.content|replace_slide_offer_variables(slideEntity.offerId)|raw }}</div>
  188. {# Type : button - Afficher un bouton avec style comme classe CSS #}
  189. {% elseif element.type == 'button' %}
  190. {% set buttonText = (element.text ?? '')|replace_slide_offer_variables(slideEntity.offerId) %}
  191. {% set buttonUrl = element.url ?? '#' %}
  192. {% set buttonStyle = element.style ?? 'style1' %}
  193. {% set buttonSize = element.size|default('medium') %}
  194. {% set hasArrow = element.hasArrow|default(false) %}
  195. {% set hasNewTab = element.hasNewTab|default(false) %}
  196. <div class="carousel-button-wrapper">
  197. <a href="{{ buttonUrl }}"
  198. class="btn {{ buttonStyle }} {{ buttonSize }} reverse_color"
  199. {% if hasNewTab %}target="_blank" rel="noopener"{% endif %}>
  200. {{ buttonText }}
  201. {% if hasArrow %}{{ getLucidIcon('arrow-right') }}{% endif %}
  202. </a>
  203. </div>
  204. {# Type : image - Afficher une image #}
  205. {% elseif element.type == 'image' %}
  206. <div class="carousel-image">
  207. <img src="{{ sulu_media_url(element.content, '1200x') }}"
  208. alt="{{ element.alt ?? '' }}"
  209. loading="lazy"
  210. style="{% if element.width is defined %}width: {{ element.width }}px;{% endif %}{% if element.height is defined %} height: {{ element.height }}px;{% endif %}">
  211. </div>
  212. {% endif %}
  213. {% endfor %}
  214. {% if slide.tags is defined and slide.tags is not empty %}
  215. <div class="content-tags">
  216. {% for tag in slide.tags %}
  217. <div class="content-tag">{{ tag.tag }}</div>
  218. {% endfor %}
  219. </div>
  220. {% endif %}
  221. </div>
  222. </div>
  223. </div>
  224. {% endfor %}
  225. </div>
  226. </div>
  227. {# Navigation avec dots (style blog) #}
  228. <div class="carousel-navigation">
  229. <span class="carousel-nav-icon carousel-nav-icon--prev" hidden>{{ getLucidIcon('chevron-left')|raw }}</span>
  230. <span class="carousel-nav-icon carousel-nav-icon--next" hidden>{{ getLucidIcon('chevron-right')|raw }}</span>
  231. </div>
  232. {# Vignettes #}
  233. {% if showThumbnails %}
  234. {% set inactiveFilter = carousel.thumbnailInactiveFilter|default('grayscale') %}
  235. <div class="carousel-thumbnails" data-carousel-id="{{ carouselId }}">
  236. {% for slideEntity in slidesToUse %}
  237. {# Utiliser les données de l'entité Slide #}
  238. {% set slideElements = slideEntity.elements ?? [] %}
  239. {% set media = null %}
  240. {% if slideEntity.mediaUrl is not empty %}
  241. {% set media = {'url': slideEntity.mediaUrl, 'thumbnails': {'200x': slideEntity.mediaUrl, '400x': slideEntity.mediaUrl}, 'isVideo': slideEntity.isVideo} %}
  242. {% elseif slideElements.media is defined and slideElements.media.id is defined %}
  243. {% set media = sulu_resolve_media(slideElements.media.id, app.request.locale) %}
  244. {% endif %}
  245. {% if media is not null %}
  246. {% set mediaUrl = sulu_media_original_url(media) %}
  247. {% set isVideo = media.isVideo is defined ? media.isVideo : (mediaUrl matches '/\.mp4(\\?.*)?$/' or mediaUrl matches '/\.webm(\\?.*)?$/' or mediaUrl matches '/\.ogg(\\?.*)?$/') %}
  248. {% else %}
  249. {% set mediaUrl = '' %}
  250. {% set isVideo = false %}
  251. {% endif %}
  252. {# Définir le nom de la destination pour cette vignette #}
  253. {% set destinationName = '' %}
  254. {% if isVideo %}
  255. {# Pour les vidéos, utiliser un canvas pour capturer le premier frame #}
  256. <div class="thumbnail {% if loop.first %}active{% endif %} filter-{{ inactiveFilter }}"
  257. data-index="{{ loop.index0 }}"
  258. data-carousel-id="{{ carouselId }}"
  259. data-video-url="{{ mediaUrl }}">
  260. <canvas class="video-thumbnail-canvas" width="{{ carousel.thumbnailWidth|default(120) }}" height="{{ carousel.thumbnailHeight|default(80) }}"></canvas>
  261. <div class="video-play-icon">▶</div>
  262. {% else %}
  263. {# Pour les images, utiliser les thumbnails normaux #}
  264. {% set mediaPoster = sulu_media_url(media, '200x') %}
  265. <div class="thumbnail {% if loop.first %}active{% endif %} filter-{{ inactiveFilter }}"
  266. data-index="{{ loop.index0 }}"
  267. data-carousel-id="{{ carouselId }}">
  268. <img src="{{ mediaPoster }}"
  269. alt="{{ media.title|default('Thumbnail ' ~ loop.index) }}"
  270. loading="lazy">
  271. {% endif %}
  272. {# Afficher le nom de la destination #}
  273. {% if destinationName is not empty %}
  274. <div class="thumbnail-label">
  275. <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
  276. <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"/>
  277. </svg>
  278. <span>{{ destinationName }}</span>
  279. </div>
  280. {% endif %}
  281. </div>
  282. {% endfor %}
  283. </div>
  284. {% endif %}
  285. </div>
  286. {% endif %}
  287. {# Le JavaScript du carousel est géré par snippets/hero-section.js (même système que hero_section) #}
  288. {# La navigation avec dots est gérée par snippets/carousel.js #}
  289. <style>
  290. #{{ carouselId }} {
  291. /* Général */
  292. --borders-radius: {{ carousel.bordersRadius|default(0) ~ carousel.bordersRadiusUnit|default('px') }};
  293. --text-width: {{ carousel.textWidth|default(100) ~ carousel.textWidthUnit|default('%') }};
  294. --height: {{ carousel.height|default(400) ~ carousel.heightUnit|default('px') }};
  295. --gap-elements: {{ carousel.gapElements|default(12) ~ carousel.gapElementsUnit|default('px') }};
  296. --carousel-padding: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') ~ ' ' ~ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
  297. --carousel-padding-top: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') }};
  298. --carousel-padding-bottom: {{ carousel.paddingTopBottom|default(0) ~ carousel.paddingUnit|default('px') }};
  299. --carousel-padding-left: {{ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
  300. --carousel-padding-right: {{ carousel.paddingLeftRight|default(0) ~ carousel.paddingUnit|default('px') }};
  301. /* Vignettes */
  302. --thumbnails-gap: {{ carousel.thumbnailGap|default(10) }}px;
  303. --thumbnails-width: {{ carousel.thumbnailWidth|default(120) }}px;
  304. --thumbnails-height: {{ carousel.thumbnailHeight|default(80) }}px;
  305. --thumbnails-border-radius: {{ carousel.thumbnailBorderRadius|default(8) }}px;
  306. --thumbnail-active-border: {{ carousel.thumbnailActiveBorderWidth|default(3) ~ 'px solid ' ~ carousel.thumbnailActiveBorderColor|default('#8B5CF6') }};
  307. --thumbnail-border: {{ carousel.thumbnailActiveBorderWidth|default(3) ~ 'px solid transparent' }};
  308. /* Vignettes responsive */
  309. --responsive-thumbnails-gap: {{ carousel.thumbnailGap|default(10) * 0.8 }}px;
  310. --responsive-thumbnails-width: {{ carousel.thumbnailWidth|default(120) * 0.9 }}px;
  311. --responsive-thumbnails-height: {{ carousel.thumbnailHeight|default(80) * 0.9 }}px;
  312. /* Filtres des vignettes inactives */
  313. --thumbnail-filter-intensity: {{ carousel.thumbnailInactiveFilterIntensity|default(50) }};
  314. --thumbnail-overlay-color: {{ carousel.thumbnailInactiveOverlayColor|default('#000000') }};
  315. /* Elements textuels des vignettes */
  316. --thumbnail-text-color: {{ carousel.thumbnailTextColor|default('#fff') }};
  317. --thumbnail-text-size: {{ carousel.thumbnailTextSize|default(12) ~ carousel.thumbnailTextSizeUnit|default('px') }};
  318. --thumbnail-text-weight: {{ carousel.thumbnailTextWeight|default(600) }};
  319. --thumbnail-text-align: {{ carousel.thumbnailTextAlign|default('left') }};
  320. --thumbnail-text-border-radius: {{ carousel.thumbnailTextBorderRadius|default(8) ~ carousel.thumbnailTextBorderRadiusUnit|default('px') }};
  321. /* Fond des étiquettes via ::after */
  322. --thumbnail-label-background: {{ carousel.thumbnailLabelBackground|default(carousel.thumbnailTextBackground|default('#000000')) }};
  323. --thumbnail-label-background-opacity: {{ carousel.thumbnailLabelBackgroundOpacity|default(carousel.thumbnailTextBackgroundOpacity|default(70)) }};
  324. --thumbnail-label-border-radius: {{ carousel.thumbnailTextBorderRadius|default(20) ~ carousel.thumbnailTextBorderRadiusUnit|default('px') }};
  325. /* Bouton */
  326. --main-color-button: {{ carousel.mainColorButton|default('#004461') }};
  327. --secondary-color-button: {{ carousel.secondaryColorButton|default('#fff') }};
  328. --borders-radius-button: {{ carousel.bordersRadiusButton|default(8) ~ carousel.bordersRadiusButtonUnit|default('px') }};
  329. --padding-button: {{ carousel.paddingSizeTopBottomButton|default(4) ~ carousel.paddingButtonUnit|default('px') ~ ' ' ~ carousel.paddingSizeLeftRightButton|default(4) ~ carousel.paddingButtonUnit|default('px') }};
  330. /* Bouton dynamique */
  331. --dynamic-button-text-color: {{ carousel.dynamicButtonTextColor|default('#fff') }};
  332. --dynamic-button-background: {{ carousel.dynamicButtonBackground|default('#004461') }};
  333. --dynamic-button-price-color: {{ carousel.dynamicButtonPriceColor|default('#000') }};
  334. --dynamic-button-border-radius: {{ carousel.dynamicButtonBorderRadius|default(8) ~ carousel.dynamicButtonBorderRadiusUnit|default('px') }};
  335. --dynamic-button-padding: {{ carousel.dynamicButtonPaddingTopBottom|default(4) ~ carousel.dynamicButtonPaddingTopBottomUnit|default('px') ~ ' ' ~ carousel.dynamicButtonPaddingRightLeft|default(4) ~ carousel.dynamicButtonPaddingRightLeftUnit|default('px') }};
  336. --dynamic-button-border: {{ carousel.dynamicButtonBorderSize|default(1) ~ carousel.dynamicButtonBorderSizeUnit|default('px') ~ ' solid ' ~ carousel.dynamicButtonBorderColor|default('#fff') }};
  337. /* Tags */
  338. --tags-color: {{ carousel.tagsColor|default('#000') }};
  339. --tags-font-weight: {{ carousel.tagsFontWeight|default(600) }};
  340. --tags-padding: {{ carousel.tagsPaddingTopBottom|default(3) ~ carousel.tagsPaddingTopBottomUnit|default('px') }} {{ carousel.tagsPaddingRightLeft|default(6) ~ carousel.tagsPaddingRightLeftUnit|default('px') }};
  341. --tags-border-radius: {{ carousel.tagsBorderRadius|default(12) ~ carousel.tagsBorderRadiusUnit|default('px') }};
  342. --tags-font-size: {{ carousel.tagsFontSize|default(10) ~ carousel.tagsFontSizeUnit|default('px') }};
  343. --tags-background-color: {{ carousel.tagsBackgroundColor|default('rgba(217, 217, 217, 0.20)') }};
  344. /* Dots */
  345. --dot-color-1: {{ carousel.dotColor1|default('#004461') }};
  346. --dot-color-2: {{ carousel.dotColor2|default('#00AAD7') }};
  347. {% 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') : '' %}
  348. {% set logoHeightUnit = carousel.logoHeight is defined and carousel.logoHeight is not empty ? carousel.logoUnit|default('px') : '' %}
  349. /* Dimensions logo compagnie */
  350. --logo-width: {{ carousel.logoWidth|default('auto') ~ logoWidthUnit }};
  351. --logo-height: {{ carousel.logoHeight|default('auto') ~ logoHeightUnit }};
  352. }
  353. </style>