templates/_snippets/hero_section.html.twig line 1

Open in your IDE?
  1. {# ------------------------------------------------------------------------ #}
  2. {# ----------------------------- PARAMÉTRAGES ----------------------------- #}
  3. {# ------------------------------------------------------------------------ #}
  4. {% set carouselId = 'hero-section-' ~ random() %}
  5. {# Booléens #}
  6. {% set isArrows = heroSection.isArrows|default(false) %}
  7. {% set isSwitchSlideTime = heroSection.isSwitchSlideTime|default(false) %}
  8. {% set isDynamicButton = heroSection.isDynamicButton|default(false) %}
  9. {% set isDynamicButtonArrow = heroSection.isDynamicButtonArrow|default(true) %}
  10. {% set isDynamicButtonBorder = heroSection.isDynamicButtonBorder|default(false) %}
  11. {# --- Paramètres du carousel --- #}
  12. {% set carouselContainerStyles = {
  13. 'border-radius': heroSection.bordersRadius|default(0) ~ heroSection.bordersRadiusUnit|default('px'),
  14. }%}
  15. {% set carouselSlideStyles = {
  16. 'height' : heroSection.height|default(400) ~ heroSection.heightUnit|default('px'),
  17. '--content-margin': -(heroSection.bordersRadius|default(0) * 2) ~ heroSection.bordersRadiusUnit|default('px')
  18. } %}
  19. {% set carouselContentStyles = {
  20. 'background-size' : heroSection.thumbnailBackgroundSize|default('cover'),
  21. 'background-position' : heroSection.thumbnailBackgroundPosition|default('center'),
  22. 'margin-right': -(heroSection.bordersRadius|default(0) * 2) ~ heroSection.bordersRadiusUnit|default('px')
  23. } %}
  24. {% set carouselTitleStyles = {
  25. 'font-weight' : '700',
  26. 'line-height' : 'normal'
  27. } %}
  28. {% set carouselLineStyles = {
  29. 'gap': heroSection.gapElements|default(0) ~ heroSection.gapElementsUnit|default('px'),
  30. } %}
  31. {# Flèches #}
  32. {% set carouselArrowsStyles = {
  33. 'height' : heroSection.height|default(400) ~ heroSection.heightUnit|default('px'),
  34. } %}
  35. {% set carouselArrowStyles = {
  36. 'color' : heroSection.arrowsColor|default('#000000'),
  37. } %}
  38. {# Bouton dynamique #}
  39. {% set carouselDynamicButtonStyles = {
  40. 'display' : 'inline-flex',
  41. 'align-items': 'center',
  42. 'padding' : heroSection.dynamicButtonPaddingTopBottom|default(4) ~ heroSection.dynamicButtonPaddingTopBottomUnit|default('px') ~ ' ' ~ heroSection.dynamicButtonPaddingRightLeft|default(4) ~ heroSection.dynamicButtonPaddingRightLeftUnit|default('px'),
  43. 'background' : heroSection.dynamicButtonBackground|default('#FFFFFF'),
  44. 'color' : heroSection.dynamicButtonTextColor|default('#005474'),
  45. 'border-radius' : heroSection.dynamicButtonBorderRadius|default(8) ~ heroSection.dynamicButtonBorderRadiusUnit|default('px'),
  46. 'border' : heroSection.isDynamicButtonBorder == true ? heroSection.dynamicButtonBorderSize|default(1) ~ heroSection.dynamicButtonBorderSizeUnit|default('px') ~ ' solid ' ~ heroSection.dynamicButtonBorderColor|default('#005474') : 'none',
  47. }%}
  48. {% set carouselDynamicButtonHoverStyles = {
  49. 'background' : heroSection.dynamicButtonTextColor|default('#005474'),
  50. 'color' : heroSection.dynamicButtonBackground|default('#FFFFFF'),
  51. }%}
  52. {% set carouselDynamicButtonPriceStyles = {
  53. 'color' : heroSection.dynamicButtonPriceColor|default('#000'),
  54. } %}
  55. {# Dots #}
  56. {% set carouselDotsActiveBeforeStyles = {
  57. 'background' : 'linear-gradient(90deg, ' ~ heroSection.dotColor1|default('#004461') ~ ', ' ~ heroSection.dotColor2|default('#00AAD7') ~ ')',
  58. }%}
  59. {# Fond des étiquettes via ::after #}
  60. {% set labelBgColor = heroSection.thumbnailLabelBackground|default('#000000') %}
  61. {% set labelBgOpacity = heroSection.thumbnailLabelBackgroundOpacity|default(70) %}
  62. {% set labelElementOpacity = heroSection.thumbnailLabelOpacity|default(100) %}
  63. {% set labelBorderRadius = heroSection.thumbnailTextBorderRadius|default(20) %}
  64. {# Filtre pour les vignettes non actives #}
  65. {% set inactiveFilter = heroSection.thumbnailInactiveFilter|default('grayscale') %}
  66. {% set filterIntensity = heroSection.thumbnailInactiveFilterIntensity|default(50) %}
  67. {% set overlayColor = heroSection.thumbnailInactiveOverlayColor|default('#000000') %}
  68. {% set mainColorButton = heroSection.mainColorButton|default('#004461') %}
  69. {% set secondaryColorButton = heroSection.secondaryColorButton|default('#fff') %}
  70. {% set bordersRadiusButton = heroSection.bordersRadiusButton|default(8) %}
  71. {% set bordersRadiusButtonUnit = heroSection.bordersRadiusButtonUnit|default('px') %}
  72. {% set paddingSizeLeftRightButton = heroSection.paddingSizeLeftRightButton|default(4) %}
  73. {% set paddingSizeTopBottomButton = heroSection.paddingSizeTopBottomButton|default(4) %}
  74. {% set paddingButtonUnit = heroSection.paddingButtonUnit|default('px') %}
  75. {% set switchSlideTime = (isSwitchSlideTime and heroSection.switchSlideTime is not null and heroSection.switchSlideTime != 0 ? heroSection.switchSlideTime * 1000 : 5000) %}
  76. {# Paramètres des vignettes #}
  77. {% set showThumbnails = heroSection.showThumbnails|default(false) %}
  78. {% set thumbnailWidth = heroSection.thumbnailWidth|default(120) %}
  79. {% set thumbnailHeight = heroSection.thumbnailHeight|default(80) %}
  80. {% set thumbnailGap = heroSection.thumbnailGap|default(10) %}
  81. {% set thumbnailBorderRadius = heroSection.thumbnailBorderRadius|default(8) %}
  82. {% set thumbnailActiveBorderColor = heroSection.thumbnailActiveBorderColor|default('#8B5CF6') %}
  83. {% set thumbnailActiveBorderWidth = heroSection.thumbnailActiveBorderWidth|default(3) %}
  84. {# Paramètres du bouton dynamique #}
  85. {% set dynamicButton = {
  86. 'dynamicButtonText': heroSection.dynamicButtonText|default('hero_section.dynamic_button_discover'|trans),
  87. 'dynamicButtonTextColor': heroSection.dynamicButtonTextColor|default('#fff'),
  88. 'dynamicButtonBackground': heroSection.dynamicButtonBackground|default('#004461'),
  89. 'dynamicButtonPriceColor': heroSection.dynamicButtonPriceColor|default('#000'),
  90. 'dynamicButtonArrowColor': heroSection.dynamicButtonArrowColor|default('#fff'),
  91. 'dynamicButtonBorderRadius': heroSection.dynamicButtonBorderRadius|default(8),
  92. 'dynamicButtonBorderRadiusUnit': heroSection.dynamicButtonBorderRadiusUnit|default('px'),
  93. 'dynamicButtonPaddingRightLeft': heroSection.dynamicButtonPaddingRightLeft|default(4),
  94. 'dynamicButtonPaddingRightLeftUnit': heroSection.dynamicButtonPaddingRightLeftUnit|default('px'),
  95. 'dynamicButtonPaddingTopBottom': heroSection.dynamicButtonPaddingTopBottom|default(4),
  96. 'dynamicButtonPaddingTopBottomUnit': heroSection.dynamicButtonPaddingTopBottomUnit|default('px'),
  97. 'dynamicButtonBorderSize': heroSection.dynamicButtonBorderSize|default(1),
  98. 'dynamicButtonBorderSizeUnit': heroSection.dynamicButtonBorderSizeUnit|default('px'),
  99. 'dynamicButtonBorderColor': heroSection.dynamicButtonBorderColor|default('#fff')
  100. } %}
  101. {% set carouselContentTagsDivContentTagStyles = {
  102. 'color' : heroSection.tagsColor|default('#000'),
  103. 'font-weight' : heroSection.tagsFontWeight|default(600),
  104. 'text-align' : heroSection.tagsTextAlign|default('left'),
  105. 'padding' : heroSection.tagsPaddingTopBottom|default(3) ~ heroSection.tagsPaddingTopBottomUnit|default('px') ~ ' ' ~ heroSection.tagsPaddingRightLeft|default(6) ~ heroSection.tagsPaddingRightLeftUnit|default('px'),
  106. 'border-radius' : heroSection.tagsBorderRadius|default(12) ~ heroSection.tagsBorderRadiusUnit|default('px'),
  107. 'font-size' : heroSection.tagsFontSize|default(10) ~ heroSection.tagsFontSizeUnit|default('px'),
  108. 'background' : heroSection.tagsBackgroundColor|default('rgba(217, 217, 217, 0.20)'),
  109. } %}
  110. {# Paramètres du fond du contenu #}
  111. {% set backgroundBlur = heroSection.backgroundBlur|default(0) %}
  112. {% set isBackgroundFilter = heroSection.isBackgroundFilter|default(false) %}
  113. {% set backgroundFilterColor = isBackgroundFilter == true ? heroSection.backgroundFilterColor|default('#000000') : '#000000' %}
  114. {% set backgroundFilterOpacity = isBackgroundFilter == true ? heroSection.backgroundFilterOpacity|default(0.4) : 0 %}
  115. {% set carouselContentBeforeStyles = {
  116. 'background' : backgroundFilterColor | hex_to_rgba(backgroundFilterOpacity),
  117. } %}
  118. {# Paramètres des prix et durées #}
  119. {% set badgesStyles = {
  120. 'border-radius' : heroSection.badgesBorderRadius|default(6) ~ heroSection.badgesBorderRadiusUnit|default('px'),
  121. 'padding' : heroSection.badgesPaddingTopBottom|default(3) ~ heroSection.badgesPaddingTopBottomUnit|default('px') ~ ' ' ~ heroSection.badgesPaddingRightLeft|default(6) ~ heroSection.badgesPaddingRightLeftUnit|default('px'),
  122. 'font-weight' : heroSection.badgesFontWeight|default(600),
  123. 'font-size' : heroSection.badgesFontSize|default(1) ~ heroSection.badgesFontSizeUnit|default('rem'),
  124. 'background-color' : heroSection.badgesBackgroundColor|default('#FF5C00'),
  125. 'color' : heroSection.badgesColor|default('#FFFFFF'),
  126. 'margin' : 'auto ' ~ heroSection.badgesMarginRightLeft|default(3) ~ heroSection.badgesMarginRightLeftUnit|default('px'),
  127. }%}
  128. {# Paramètres des logos #}
  129. {% set logosStyles = {
  130. 'max-width' : heroSection.logoWidth|default(60) ~ heroSection.logoWidthUnit|default('px'),
  131. 'max-height' : heroSection.logoHeight|default(60) ~ heroSection.logoHeightUnit|default('px'),
  132. } %}
  133. {# Durée de l'animation entre deux slides #}
  134. {% set animation = {
  135. 'contentDuration' : heroSection.animationContentDuration|default(0),
  136. 'contentDelay' : heroSection.animationContentDelay|default(0) ,
  137. 'mediaDuration' : heroSection.animationMediaDuration|default(0),
  138. 'mediaDelay' : heroSection.animationMediaDelay|default(0),
  139. }%}
  140. {# Collecter les styles mobile pour les carousel-text #}
  141. {% set mobileStyles = [] %}
  142. {% set globalTextIndex = 0 %}
  143. {# Paramètres de la légende #}
  144. {% set legendStyles = {
  145. 'color' : heroSection.legendColor|default('#000'),
  146. 'font-size' : heroSection.legendFontSize|default(1) ~ heroSection.legendFontSizeUnit|default('rem'),
  147. 'font-weight' : heroSection.legendFontWeight|default(600),
  148. 'text-align' : heroSection.legendAlign|default('left'),
  149. 'padding' : heroSection.legendPaddingTop|default(3) ~ heroSection.legendPaddingTopBottomUnit|default('px') ~ ' 0 ' ~ heroSection.legendPaddingBottom|default(3) ~ heroSection.legendPaddingBottomUnit|default('px') ~ ' 0',
  150. } %}
  151. {# Première slide hero : URL pour <link rel="preload"> (LCP) #}
  152. {% set firstHeroSlideEntity = (slidesByType['hero-section']|default([]))|first %}
  153. {% set heroLcpPreloadHref = null %}
  154. {% if firstHeroSlideEntity %}
  155. {% set se0 = firstHeroSlideEntity.elements ?? [] %}
  156. {% set med0 = null %}
  157. {% if firstHeroSlideEntity.mediaUrl is not empty %}
  158. {% set med0 = {'url': firstHeroSlideEntity.mediaUrl, 'thumbnails': {'800x': firstHeroSlideEntity.mediaUrl, '1200x': firstHeroSlideEntity.mediaUrl}, 'isVideo': firstHeroSlideEntity.isVideo} %}
  159. {% elseif se0.media is defined and se0.media.id is defined %}
  160. {% set med0 = sulu_resolve_media(se0.media.id, app.request.locale) %}
  161. {% endif %}
  162. {% if med0 is not null %}
  163. {% set mUrl0 = med0.url %}
  164. {% set isVid0 = med0.isVideo is defined ? med0.isVideo : (mUrl0 matches '/\.mp4(\\?.*)?$/' or mUrl0 matches '/\.webm(\\?.*)?$/' or mUrl0 matches '/\.ogg(\\?.*)?$/') %}
  165. {% if not isVid0 %}
  166. {% set heroLcpPreloadHref = sulu_media_url(med0, '1200x') %}
  167. {% else %}
  168. {% set poster0 = sulu_media_url(med0, '1200x') %}
  169. {% if poster0 and not (poster0 matches '/\.(mp4|webm|ogg)(\\?.*)?$/i') %}
  170. {% set heroLcpPreloadHref = poster0 %}
  171. {% endif %}
  172. {% endif %}
  173. {% endif %}
  174. {% endif %}
  175. {% if true %}
  176. {% if heroSection.isLegend == true and heroSection.legend is not empty and mode is not defined or mode != 'mobile' %}
  177. <div class="carousel-legend carousel-legend-{{ carouselId }}" carousel-id="{{ carouselId }}">
  178. <h1 class="carousel-legend-content carousel-legend-{{ carouselId }}">
  179. {{ heroSection.legend }}
  180. </h1>
  181. </div>
  182. {% endif %}
  183. <div class="carousel-wrapper"
  184. data-type="heroSection"
  185. id="{{ carouselId }}"
  186. data-carousel-id="{{ carouselId }}"
  187. data-carousel-options='{{ {
  188. "showDots": heroSection.showDots|default(false),
  189. "showThumbnails": showThumbnails,
  190. "isAutoSlide": isSwitchSlideTime,
  191. "animation": animation
  192. }|json_encode|raw }}'
  193. style="--switch-slide-time: {{ switchSlideTime }}ms;">
  194. {% if heroLcpPreloadHref %}
  195. <link rel="preload" as="image" href="{{ heroLcpPreloadHref }}" fetchpriority="high">
  196. {% endif %}
  197. <div class="carousel-container">
  198. <div class="carousel" data-observed>
  199. {% for slideEntity in slidesByType['hero-section'] ?? [] %}
  200. {# ============================================================ #}
  201. {# RECONSTRUCTION DE LA STRUCTURE SLIDE DEPUIS L'ENTITÉ SLIDE #}
  202. {# ============================================================ #}
  203. {# Extraire les données de l'entité Slide #}
  204. {% set slideElements = slideEntity.elements ?? [] %}
  205. {% set slideStyles = slideEntity.styles ?? {} %}
  206. {# Gestion du média : mediaUrl de l'entité ou depuis elements #}
  207. {% set media = null %}
  208. {% if slideEntity.mediaUrl is not empty %}
  209. {% set media = {'url': slideEntity.mediaUrl, 'thumbnails': {'800x': slideEntity.mediaUrl}, 'isVideo': slideEntity.isVideo} %}
  210. {% elseif slideElements.media is defined and slideElements.media.id is defined %}
  211. {% set media = sulu_resolve_media(slideElements.media.id, app.request.locale) %}
  212. {% endif %}
  213. {% if media is not null %}
  214. {% set mediaUrl = sulu_media_original_url(media) %}
  215. {% set mediaPoster = sulu_media_url(media, '1200x') %}
  216. {% set mediaThumb800 = sulu_media_url(media, '800x') %}
  217. {% set mediaThumb1200 = sulu_media_url(media, '1200x') %}
  218. {% set mediaThumb1920 = sulu_media_url(media, '1600x') %}
  219. {# Utiliser isVideo depuis la base de données si disponible, sinon deviner depuis l'extension #}
  220. {% set isVideo = media.isVideo is defined ? media.isVideo : (mediaUrl matches '/\.mp4(\\?.*)?$/' or mediaUrl matches '/\.webm(\\?.*)?$/' or mediaUrl matches '/\.ogg(\\?.*)?$/') %}
  221. {% else %}
  222. {% set mediaUrl = '' %}
  223. {% set mediaPoster = '' %}
  224. {% set mediaThumb800 = null %}
  225. {% set mediaThumb1200 = null %}
  226. {% set mediaThumb1920 = null %}
  227. {% set isVideo = false %}
  228. {% endif %}
  229. {# Reconstruire la structure slide complète #}
  230. {% set slide = {
  231. 'media': media,
  232. 'title': slideEntity.subtitle ?? '',
  233. 'isColorGradient': slideStyles.isColorGradient ?? false,
  234. 'isCustomBackgroundFilter': slideStyles.isCustomBackgroundFilter ?? false,
  235. 'customBackgroundFilterColor': slideStyles.customBackgroundFilterColor ?? '#000000',
  236. 'customBackgroundFilterOpacity': slideStyles.customBackgroundFilterOpacity ?? 40,
  237. 'gradientOrientation': slideStyles.gradientOrientation ?? 'leftRight',
  238. 'color1': slideStyles.color1 ?? '#fff',
  239. 'color1Opacity': slideStyles.color1Opacity ?? 100,
  240. 'color2': slideStyles.color2 ?? '#000',
  241. 'color2Opacity': slideStyles.color2Opacity ?? 100,
  242. 'isCommonCompanySelector': slideEntity.companyId is not empty,
  243. 'CommonCompanySelector': slideEntity.companyId ?? '',
  244. 'companyCustomLogo': slideElements.companyCustomLogo ?? null,
  245. 'isDestinationSelector': false,
  246. 'destinationSelector': '',
  247. 'destinationCustomName': '',
  248. 'contents': slideElements is iterable ? slideElements : [],
  249. 'isNoCustom': false,
  250. 'isCustomDuration': false,
  251. 'customDuration': '',
  252. 'isCustomPrice': false,
  253. 'customPrice': '',
  254. 'advicedDuration': slideEntity.subtitle ?? '',
  255. 'advicedPeriod': '',
  256. 'bonusTexts': []
  257. } %}
  258. {# Définir le nom de la destination au début du slide #}
  259. {% set destinationName = '' %}
  260. {% if slide.isDestinationSelector and slide.destinationCustomName is not empty %}
  261. {% set destinationName = slide.destinationCustomName %}
  262. {% elseif slide.isDestinationSelector and slide.destinationSelector is not empty %}
  263. {% set destinationName = 'Destination' %}
  264. {% endif %}
  265. {% set isFullSize = slideStyles.fullSize|default(false) %}
  266. {% set slideHref = '' %}
  267. {% set slideOpensNewTab = false %}
  268. {% for element in slide.contents %}
  269. {% if slideHref == '' and element.type is defined and element.type == 'button' %}
  270. {% set candidateUrl = (element.url ?? '')|trim %}
  271. {% if candidateUrl is not empty and candidateUrl != '#' %}
  272. {% set slideHref = candidateUrl %}
  273. {% set slideOpensNewTab = element.hasNewTab|default(false) %}
  274. {% endif %}
  275. {% endif %}
  276. {% endfor %}
  277. <div class="carousel-slide{% if slideHref %} is-linked{% endif %}{% if isFullSize %} is-full-size{% endif %}"
  278. id="slide-{{ carouselId }}-{{ loop.index0 }}"
  279. {% if slideHref %}
  280. data-slide-href="{{ slideHref|e('html_attr') }}"
  281. {% if slideOpensNewTab %}data-slide-target="_blank"{% endif %}
  282. {% endif %}>
  283. {# Media - toujours affiché, même avec gradient #}
  284. {% if isVideo %}
  285. <video class="carousel-media"
  286. autoplay muted loop playsinline
  287. preload="metadata"
  288. poster="{{ mediaPoster }}"
  289. {% if loop.first %}fetchpriority="high"{% endif %}>
  290. <source src="{{ mediaUrl }}" type="video/mp4">
  291. <p>{{ 'carousel.destination.video_not_supported'|trans }}</p>
  292. </video>
  293. {% else %}
  294. {% set isHeroLcpImage = loop.first and mediaPoster is not empty %}
  295. <img class="carousel-media{{ isHeroLcpImage ? ' carousel-media--lcp' : '' }}"
  296. src="{{ mediaPoster }}"
  297. {% if mediaThumb800 and mediaThumb1200 and mediaThumb800 != mediaThumb1200 %}
  298. srcset="{{ mediaThumb800 }} 800w{% if mediaThumb1200 %}, {{ mediaThumb1200 }} 1200w{% endif %}{% if mediaThumb1920 %}, {{ mediaThumb1920 }} 1920w{% endif %}"
  299. sizes="100vw"
  300. {% elseif mediaThumb800 %}
  301. srcset="{{ mediaThumb800 }} 800w"
  302. sizes="100vw"
  303. {% endif %}
  304. alt="{{ slide.title|default('')|striptags|e('html_attr') }}"
  305. decoding="async"
  306. {% if isHeroLcpImage %}
  307. fetchpriority="high"
  308. {% else %}
  309. loading="lazy" fetchpriority="low"
  310. {% endif %}>
  311. {% endif %}
  312. {% if isFullSize and slideHref %}
  313. <a class="carousel-slide-link"
  314. href="{{ slideHref }}"
  315. {% if slideOpensNewTab %}target="_blank" rel="noopener"{% endif %}
  316. aria-label="{{ slide.title|default('')|striptags|e('html_attr') }}"></a>
  317. {% endif %}
  318. {% if not isFullSize %}
  319. <div class="carousel-content {% if slide.isColorGradient %}color-gradient{% endif %}">
  320. {# Image de fond avec filtre blur ou gradient selon la configuration #}
  321. {% if slide.isColorGradient %}
  322. {% set gradientDirections = {
  323. 'leftRight': 'to right',
  324. 'rightLeft': 'to left',
  325. 'topBottom': 'to bottom',
  326. 'bottomTop': 'to top',
  327. 'topLeftBottomRight': 'to bottom right',
  328. 'bottomLeftTopRight': 'to top right',
  329. 'topRightBottomLeft': 'to bottom left',
  330. 'bottomRightTopLeft': 'to top left'
  331. } %}
  332. {% set gradientDirection = gradientDirections[slide.gradientOrientation] %}
  333. <div class="background-blur" style="
  334. background: linear-gradient({{ gradientDirection }}, {{ slide.color1|hex_to_rgba(slide.color1Opacity) }}, {{ slide.color2|hex_to_rgba(slide.color2Opacity) }});
  335. "></div>
  336. {% else %}
  337. {% if isVideo %}
  338. <div class="background-blur" style="
  339. filter: blur({{ backgroundBlur > 0 ? backgroundBlur : 40 }}px);
  340. ">
  341. <canvas class="video-background-canvas"
  342. data-video-url="{{ mediaUrl }}">
  343. </canvas>
  344. </div>
  345. {% else %}
  346. <div class="background-blur" style="
  347. background-image: url('{{ mediaPoster }}');
  348. filter: blur({{ backgroundBlur > 0 ? backgroundBlur : 40 }}px);
  349. "></div>
  350. {% endif %}
  351. {% endif %}
  352. {# Contenu sans filtre blur #}
  353. <div class="line" style="position: relative; z-index: 1;">
  354. {# Affichage du logo de la compagnie si sélectionnée #}
  355. {% if slide.isCommonCompanySelector and slide.CommonCompanySelector is not empty %}
  356. <div class="content-company-logo">
  357. {% if slide.companyCustomLogo is not empty and slide.companyCustomLogo.url is defined %}
  358. <img src="{{ slide.companyCustomLogo.url }}" alt="Logo compagnie">
  359. {% else %}
  360. {% set companyLogo = get_company_logo_by_id(slide.CommonCompanySelector) %}
  361. {% if companyLogo is not empty %}
  362. <img src="{{ companyLogo }}" alt="Logo compagnie">
  363. {% endif %}
  364. {% endif %}
  365. </div>
  366. {% endif %}
  367. {# Parcourir les éléments dans l'ordre selon slide.elements #}
  368. <div class="carousel-title">{{ slide.title|replace_slide_offer_variables(slideEntity.offerId)|raw }}</div>
  369. {% for element in slide.contents %}
  370. {# Type : text - Afficher un texte simple #}
  371. {% if element.type == 'text' %}
  372. <div class="carousel-text">{{ element.content|replace_slide_offer_variables(slideEntity.offerId)|raw }}</div>
  373. {# Type : button - Afficher un bouton avec style comme classe CSS #}
  374. {% elseif element.type == 'button' %}
  375. {% set buttonText = (element.text ?? '')|replace_slide_offer_variables(slideEntity.offerId) %}
  376. {% set buttonUrl = element.url ?? '#' %}
  377. {% set buttonStyle = element.style ?? 'style1' %}
  378. {% set buttonSize = element.size|default('medium') %}
  379. {% set hasArrow = element.hasArrow|default(false) %}
  380. {% set hasNewTab = element.hasNewTab|default(false) %}
  381. <div class="carousel-button-wrapper">
  382. <a href="{{ buttonUrl }}"
  383. class="btn {{ buttonStyle }} {{ buttonSize }} reverse_color"
  384. {% if hasNewTab %}target="_blank" rel="noopener"{% endif %}>
  385. {{ buttonText }}
  386. {% if hasArrow %}{{ getLucidIcon('arrow-right') }}{% endif %}
  387. </a>
  388. </div>
  389. {# Type : image - Afficher une image #}
  390. {% elseif element.type == 'image' %}
  391. <div class="carousel-image">
  392. <img src="{{ sulu_media_url(element.content, '1200x') }}"
  393. alt="{{ element.alt ?? '' }}"
  394. loading="lazy"
  395. style="{% if element.width is defined %}width: {{ element.width }}px;{% endif %}{% if element.height is defined %} height: {{ element.height }}px;{% endif %}">
  396. </div>
  397. {% endif %}
  398. {% endfor %}
  399. </div>
  400. </div>
  401. {% endif %}
  402. </div>
  403. {% endfor %}
  404. </div>
  405. </div>
  406. {# Dots #}
  407. {% if heroSection.showDots|default(false) %}
  408. <div class="carousel-dots">
  409. {% for slideEntity in slidesByType['hero-section'] ?? [] %}
  410. <span class="dot {% if loop.first %}active{% endif %}"
  411. data-index="{{ loop.index0 }}"
  412. data-carousel-id="{{ carouselId }}"></span>
  413. {% endfor %}
  414. </div>
  415. {% endif %}
  416. {# Flèches #}
  417. {% if isArrows %}
  418. <div class="carousel-arrows">
  419. <button class="carousel-arrow left" aria-label="{{ 'common.previous'|trans }}" data-carousel-id="{{ carouselId }}">{{ getLucidIcon('chevron-left') }}</button>
  420. <button class="carousel-arrow right" aria-label="{{ 'common.next'|trans }}" data-carousel-id="{{ carouselId }}">{{ getLucidIcon('chevron-right') }}</button>
  421. </div>
  422. {% endif %}
  423. </div>
  424. {% endif %}
  425. {# Le JavaScript du carousel est maintenant géré par assets/website/js/snippets/hero-section.js #}
  426. <!-- Styles dynamiques -->
  427. <style>
  428. {% if heroSection.isLegend == true and heroSection.legend is not empty and mode is not defined or mode != 'mobile' %}
  429. h1.carousel-legend-{{ carouselId }}, .carousel-legend-{{ carouselId }}{
  430. {{ legendStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  431. }
  432. {% endif %}
  433. #{{ carouselId }} .carousel-container {
  434. {{ carouselContainerStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  435. }
  436. #{{ carouselId }} .carousel-slide {
  437. {{ carouselSlideStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  438. }
  439. #{{ carouselId }} .carousel-title {
  440. {{ carouselTitleStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  441. }
  442. #{{ carouselId }} .dynamic-button {
  443. {{ carouselDynamicButtonStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  444. }
  445. #{{ carouselId }} .dynamic-button svg {
  446. color: {{ heroSection.dynamicButtonArrowColor|default('inherit') }};
  447. }
  448. #{{ carouselId }} .dynamic-button .price {
  449. {{ carouselDynamicButtonPriceStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  450. }
  451. #{{ carouselId }} .carousel-content {
  452. {{ carouselContentStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  453. }
  454. #{{ carouselId }} .carousel-content:not([class*="color-gradient"])::before {
  455. {{ carouselContentBeforeStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  456. }
  457. {# Styles personnalisés pour les slides avec filtre de fond personnalisé #}
  458. {% for slideEntity in slidesByType['hero-section'] ?? [] %}
  459. {% set slideStyles = slideEntity.styles ?? {} %}
  460. {% if slideStyles.isCustomBackgroundFilter ?? false %}
  461. #{{ carouselId }} #slide-{{ carouselId }}-{{ loop.index0 }} .carousel-content::before {
  462. background: {{ (slideStyles.customBackgroundFilterColor ?? '#000000') | hex_to_rgba(slideStyles.customBackgroundFilterOpacity ?? 40) }};
  463. }
  464. {% endif %}
  465. {% endfor %}
  466. #{{ carouselId }} .carousel .line {
  467. {{ carouselLineStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  468. }
  469. #{{ carouselId }} .dot.active::before {
  470. {{ carouselDotsActiveBeforeStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  471. }
  472. #{{ carouselId }} .carousel-arrows {
  473. {{ carouselArrowsStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  474. }
  475. #{{ carouselId }} .carousel-arrow {
  476. {{ carouselArrowStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  477. }
  478. #{{ carouselId }} .carousel-media, #{{ carouselId }} .carousel-content {
  479. border-radius: {{ heroSection.bordersRadius|default(0) ~ heroSection.bordersRadiusUnit|default('px') }};
  480. }
  481. #{{ carouselId }} .duration-badge, #{{ carouselId }} .price-badge {
  482. {{ badgesStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  483. }
  484. #{{ carouselId }} .content-company-logo {
  485. {{ logosStyles|map((value, key) => key ~ ':' ~ value)|join('; ') }};
  486. }
  487. @media (max-width: 768px) {
  488. #{{ carouselId }} .carousel-slide,
  489. #{{ carouselId }} .carousel-content,
  490. #{{ carouselId }} .carousel .line {
  491. height: auto;
  492. }
  493. #{{ carouselId }} .carousel-media, #{{ carouselId }} .carousel-content {
  494. border-radius: 0;
  495. }
  496. #{{ carouselId }} .carousel-button-wrapper .btn {
  497. width: 100%;
  498. }
  499. }
  500. /* Animations */
  501. @keyframes fillGradient {
  502. 0% {
  503. width: 0%;
  504. }
  505. 100% {
  506. width: 100%;
  507. }
  508. }
  509. /* Responsive mobile - Réduction des font-size */
  510. @media (max-width: 768px) {
  511. {% for mobileStyle in mobileStyles %}
  512. {{ mobileStyle }}
  513. {% endfor %}
  514. }
  515. </style>