1(function ($) { 2 "use strict"; 3 4 let indyAcc4InstanceSeq = 0; 5 6 // Fonction pour initialiser sur desktop (>= 1024px) 7 function initAccompagnement4WidgetDesktop() { 8 if (window.innerWidth < 1024) { 9 $(".indy-accompagnement-4-widget").each(function () { 10 this.style.removeProperty("--indy-acc4-fixed-img-sticky-top"); 11 }); 12 $(".indy-accompagnement-4-widget").removeClass("is-media-hidden"); 13 return; 14 } 15 const $widget = $(".indy-accompagnement-4-widget"); 16 if (!$widget.length) return; 17 18 // Ãviter de dupliquer les listeners si Elementor ré-initialise le widget 19 if (!$widget.data("indyAcc4Id")) { 20 indyAcc4InstanceSeq += 1; 21 $widget.data("indyAcc4Id", indyAcc4InstanceSeq); 22 } 23 const instanceId = $widget.data("indyAcc4Id"); 24 const scrollNs = `scroll.indyAcc4.${instanceId}`; 25 const resizeNs = `resize.indyAcc4.${instanceId}`; 26 27 const $mainWrapper = $widget.find(".main-wrapper"); 28 const $fixedImgWrapper = $widget.find(".fixed-img-wrapper"); 29 const $items = $widget.find(".content-wrapper .item"); 30 31 if (!$mainWrapper.length || !$fixedImgWrapper.length || !$items.length) { 32 return; 33 } 34 35 function getMediaForItem($item) { 36 const svgUrl = $item.attr("data-anim"); 37 if (svgUrl) { 38 return { type: "svg", url: svgUrl }; 39 } 40 41 const explicitImg = 42 $item.attr("data-img") || 43 $item.attr("data-image") || 44 $item.attr("data-image-url"); 45 if (explicitImg) { 46 return { type: "img", url: explicitImg }; 47 } 48 49 // Fallback: prendre la première image "réelle" de l'item (pas le fallback de l'object) 50 const $img = $item 51 .find(".item-img img") 52 .filter(function () { 53 return !$(this).closest("object.svg-animation").length; 54 }) 55 .first(); 56 57 const imgUrl = $img.attr("src") || $img.attr("data-src"); 58 if (!imgUrl) return null; 59 60 return { type: "img", url: imgUrl }; 61 } 62 63 function ensureFixedSvgObject() { 64 let $obj = $fixedImgWrapper.find("object.svg-animation").first(); 65 if ($obj.length) return $obj; 66 67 const obj = document.createElement("object"); 68 obj.type = "image/svg+xml"; 69 obj.setAttribute("data", ""); 70 obj.setAttribute("aria-label", "Accompagnement"); 71 obj.className = "svg-animation"; 72 obj.style.display = "none"; 73 74 const fallbackImg = document.createElement("img"); 75 fallbackImg.src = ""; 76 fallbackImg.alt = "Accompagnement"; 77 fallbackImg.loading = "eager"; 78 obj.appendChild(fallbackImg); 79 80 $fixedImgWrapper.append(obj); 81 $obj = $(obj); 82 return $obj; 83 } 84 85 function ensureFixedImgElement() { 86 let $img = $fixedImgWrapper.find("img.fixed-media").first(); 87 if ($img.length) return $img; 88 89 const img = document.createElement("img"); 90 img.className = "fixed-media"; 91 img.alt = "Accompagnement"; 92 img.loading = "eager"; 93 img.style.display = "none"; 94 $fixedImgWrapper.append(img); 95 $img = $(img); 96 return $img; 97 } 98 99 // Fonction pour changer le SVG 100 function changeSvg(svgUrl) { 101 const $currentSvgObject = ensureFixedSvgObject(); 102 if (!$currentSvgObject.length) return; 103 104 const currentData = $currentSvgObject.attr("data"); 105 if (currentData === svgUrl) return; // Déjà le bon SVG 106 107 // Vérifier si c'est le premier chargement (objet vide ou caché) 108 const isFirstLoad = 109 !currentData || currentData === "" || $currentSvgObject.is(":hidden"); 110 111 // Créer un nouvel object avec l'élément DOM natif pour s'assurer que l'attribut data est correctement défini 112 // jQuery ne définit pas correctement l'attribut 'data' sur les éléments <object> 113 const newObject = document.createElement("object"); 114 newObject.type = "image/svg+xml"; 115 newObject.setAttribute("data", svgUrl); // Utiliser setAttribute pour l'attribut data 116 newObject.setAttribute("aria-label", "Accompagnement"); 117 newObject.className = "svg-animation"; 118 119 // Fallback img 120 const fallbackImg = document.createElement("img"); 121 fallbackImg.src = svgUrl; 122 fallbackImg.alt = "Accompagnement"; 123 fallbackImg.loading = "eager"; 124 newObject.appendChild(fallbackImg); 125 126 // Ãcouter l'événement load pour s'assurer que le SVG est complètement chargé 127 newObject.addEventListener("load", function () { 128 // Afficher l'objet s'il s'agit du premier chargement 129 if (isFirstLoad) { 130 newObject.style.display = ""; 131 } 132 133 // Forcer le redémarrage des animations en accédant au contenu SVG 134 try { 135 const svgDoc = newObject.contentDocument; 136 if (svgDoc) { 137 // Les animations SVG devraient redémarrer automatiquement 138 // Mais on peut forcer un reflow pour s'assurer 139 svgDoc.documentElement.offsetHeight; 140 } 141 } catch (e) { 142 // Ignorer les erreurs CORS si le SVG est sur un autre domaine 143 } 144 }); 145 146 // Remplacer l'ancien object 147 $currentSvgObject.replaceWith(newObject); 148 } 149 150 function changeFixedImage(imgUrl) { 151 const $img = ensureFixedImgElement(); 152 if (!$img.length) return; 153 154 const currentSrc = $img.attr("src"); 155 if (currentSrc === imgUrl) return; 156 157 $img.attr("src", imgUrl); 158 $img.show(); 159 } 160 161 function showSvgMode(svgUrl) {
162 const $img = $fixedImgWrapper.find("img.fixed-media"); 163 if ($img.length) $img.hide(); 164 165 const $obj = ensureFixedSvgObject(); 166 if ($obj.length) $obj.show(); 167 if (svgUrl) changeSvg(svgUrl); 168 } 169 170 function showImgMode(imgUrl) { 171 const $obj = $fixedImgWrapper.find("object.svg-animation"); 172 if ($obj.length) $obj.hide(); 173 174 changeFixedImage(imgUrl); 175 } 176 177 const fadeEndNs = `transitionend.indyAcc4Fade.${instanceId}`; 178 179 function parseCssDurationMs(raw, fallbackMs) { 180 if (!raw) return fallbackMs; 181 const value = raw.trim(); 182 if (value.endsWith("ms")) 183 return Number(value.replace("ms", "")) || fallbackMs; 184 if (value.endsWith("s")) 185 return Math.round( 186 (Number(value.replace("s", "")) || fallbackMs / 1000) * 1000, 187 ); 188 return Number(value) || fallbackMs; 189 } 190 191 function getFadeOutDurationMs() { 192 const el = $fixedImgWrapper[0]; 193 if (!el) return 55; 194 const css = window.getComputedStyle(el); 195 const raw = 196 css.getPropertyValue("--indy-acc4-fade-out")?.trim() || "55ms"; 197 return parseCssDurationMs(raw, 55); 198 } 199 200 let transitionTimeout = null; 201 let isTransitioning = false; 202 let pendingMedia = null; 203 204 function clearFadeOutSchedule() { 205 $fixedImgWrapper.off(fadeEndNs); 206 if (transitionTimeout) { 207 clearTimeout(transitionTimeout); 208 transitionTimeout = null; 209 } 210 } 211 212 function scheduleFadeOutEnd() { 213 const completeFadeOut = function () { 214 if (!isTransitioning) return; 215 clearFadeOutSchedule(); 216 finishTransition(); 217 }; 218 219 const fadeOutMs = getFadeOutDurationMs(); 220 $fixedImgWrapper.on(fadeEndNs, "object, img", function (e) { 221 if (e.propertyName !== "opacity") return; 222 completeFadeOut(); 223 }); 224 transitionTimeout = window.setTimeout(completeFadeOut, fadeOutMs + 40); 225 } 226 227 function applyFixedMedia(media) { 228 const nextKey = `${media.type}:${media.url}`; 229 if (media.type === "svg") showSvgMode(media.url); 230 else showImgMode(media.url); 231 $fixedImgWrapper.data("indyAcc4CurrentMediaKey", nextKey); 232 } 233 234 function finishTransition() { 235 clearFadeOutSchedule(); 236 237 const target = pendingMedia; 238 pendingMedia = null; 239 240 if (target) applyFixedMedia(target); 241 242 window.requestAnimationFrame(function () { 243 $fixedImgWrapper.removeClass("is-fading"); 244 isTransitioning = false; 245 246 // Nouvelle cible reçue pendant le fade-in : enchaîner 247 if (pendingMedia) { 248 const currentKey = $fixedImgWrapper.data("indyAcc4CurrentMediaKey"); 249 const pendingKey = `${pendingMedia.type}:${pendingMedia.url}`; 250 if (currentKey !== pendingKey) transitionToMedia(pendingMedia); 251 } 252 }); 253 } 254 255 function transitionToMedia(media) { 256 if (!media || !media.url) return; 257 258 const nextKey = `${media.type}:${media.url}`; 259 const currentKey = $fixedImgWrapper.data("indyAcc4CurrentMediaKey"); 260 261 pendingMedia = media; 262 263 if (currentKey === nextKey) { 264 if ($fixedImgWrapper.hasClass("is-fading")) finishTransition(); 265 else pendingMedia = null; 266 return; 267 } 268 269 // Transition déjà en cours : mettre à jour la cible, ne pas annuler le fade-out 270 if (isTransitioning) { 271 if (!transitionTimeout) scheduleFadeOutEnd(); 272 return; 273 } 274 275 // Première fois: pas de fade-out (évite le flash) 276 if (!currentKey) { 277 applyFixedMedia(media); 278 pendingMedia = null; 279 return; 280 } 281 282 isTransitioning = true; 283 $fixedImgWrapper.addClass("is-fading"); 284 scheduleFadeOutEnd(); 285 } 286 287 function preloadDesktopMedia() { 288 const seen = new Set(); 289 $items.each(function () { 290 const media = getMediaForItem($(this)); 291 if ( 292 !media || 293 media.type !== "img" || 294 !media.url || 295 seen.has(media.url) 296 ) { 297 return; 298 } 299 seen.add(media.url); 300 const img = new Image(); 301 img.decoding = "async"; 302 img.src = media.url; 303 }); 304 } 305 306 function getVisibleFixedMediaBottom(fixedImgWrapper) { 307 let mediaBottom = fixedImgWrapper.getBoundingClientRect().bottom; 308 const mediaNodes = fixedImgWrapper.querySelectorAll( 309 "object.svg-animation, img.fixed-media", 310 ); 311
312 mediaNodes.forEach((el) => { 313 const style = window.getComputedStyle(el); 314 if (style.display === "none" || style.visibility === "hidden") { 315 return; 316 } 317 const rect = el.getBoundingClientRect(); 318 if (rect.height > 0) { 319 mediaBottom = Math.max(mediaBottom, rect.bottom); 320 } 321 }); 322 323 return mediaBottom; 324 } 325 326 function getHeaderHeight() { 327 const header = document.querySelector( 328 ".elementor-location-header .header-shortcode-container", 329 ); 330 return header ? header.getBoundingClientRect().height : 0; 331 } 332 333 function getVisibleFixedMediaHeight(fixedImgWrapper) { 334 let mediaHeight = fixedImgWrapper.getBoundingClientRect().height; 335 const mediaNodes = fixedImgWrapper.querySelectorAll( 336 "object.svg-animation, img.fixed-media", 337 ); 338 339 mediaNodes.forEach((el) => { 340 const style = window.getComputedStyle(el); 341 if (style.display === "none" || style.visibility === "hidden") { 342 return; 343 } 344 const rect = el.getBoundingClientRect(); 345 if (rect.height > 0) { 346 mediaHeight = Math.max(mediaHeight, rect.height); 347 } 348 }); 349 350 return mediaHeight > 0 ? mediaHeight : 487; 351 } 352 353 function updateFixedImgStickyTop() { 354 if (window.innerWidth < 1024) { 355 $widget[0].style.removeProperty("--indy-acc4-fixed-img-sticky-top"); 356 return; 357 } 358 359 const fixedImgEl = $fixedImgWrapper[0]; 360 if (!fixedImgEl) return; 361 362 const headerHeight = getHeaderHeight(); 363 const mediaHeight = getVisibleFixedMediaHeight(fixedImgEl); 364 const idealTop = (window.innerHeight + headerHeight - mediaHeight) / 2; 365 const stickyTop = Math.max( 366 headerHeight, 367 Math.min(idealTop, window.innerHeight - mediaHeight), 368 ); 369 370 $widget[0].style.setProperty( 371 "--indy-acc4-fixed-img-sticky-top", 372 `${stickyTop}px`, 373 ); 374 } 375 376 // Fonction pour déterminer l'item le plus proche du centre du viewport 377 function updateSvgOnScroll() { 378 if (window.innerWidth < 1024) { 379 $widget.removeClass("is-media-hidden"); 380 return; 381 } 382 383 // Masquer quand le bas du main-wrapper atteint le média sticky ; 384 // réafficher dès qu'on rescrolle au-dessus de ce seuil. 385 // MEDIA_HIDE_OFFSET compense le padding-bottom du .sticky-container 386 // (espace entre le bas de l'image sticky et le CTA). 387 const MEDIA_HIDE_HYSTERESIS = 8; 388 const MEDIA_HIDE_OFFSET = 125; 389 const mainWrapperEl = $mainWrapper[0]; 390 const fixedImgEl = $fixedImgWrapper[0]; 391 let shouldHideMedia = false; 392 393 if (mainWrapperEl && fixedImgEl) { 394 const mainRect = mainWrapperEl.getBoundingClientRect(); 395 const mediaBottom = getVisibleFixedMediaBottom(fixedImgEl); 396 const isHidden = $widget.hasClass("is-media-hidden"); 397 const hideThreshold = mediaBottom - MEDIA_HIDE_OFFSET; 398 399 if (isHidden) { 400 shouldHideMedia = 401 mainRect.bottom <= hideThreshold + MEDIA_HIDE_HYSTERESIS; 402 } else { 403 shouldHideMedia = 404 mainRect.bottom <= hideThreshold - MEDIA_HIDE_HYSTERESIS; 405 } 406 } 407 408 $widget.toggleClass("is-media-hidden", shouldHideMedia); 409 410 const viewportCenter = window.innerHeight * 0.5; 411 let $activeItem = null; 412 let minDistance = Infinity; 413 414 $items.each(function () { 415 const $item = $(this); 416 const rect = this.getBoundingClientRect(); 417 if (rect.bottom <= 0 || rect.top >= window.innerHeight) return; 418 419 const itemCenter = (rect.top + rect.bottom) * 0.5; 420 const distance = Math.abs(itemCenter - viewportCenter); 421 if (distance < minDistance) { 422 minDistance = distance; 423 $activeItem = $item; 424 } 425 }); 426 427 // Si un item est trouvé, charger/changer le média (SVG animé ou image) 428 if ($activeItem && $activeItem.length) { 429 const media = getMediaForItem($activeItem); 430 if (!media || !media.url) return; 431 432 transitionToMedia(media); 433 } 434 } 435 436 // Scroll / resize : une mise à jour par frame (pas de debounce 50ms) 437 let scrollRaf = null; 438 const onScroll = function () { 439 if (scrollRaf) return; 440 scrollRaf = window.requestAnimationFrame(function () { 441 scrollRaf = null; 442 updateSvgOnScroll(); 443 }); 444 }; 445 446 const onResize = function () { 447 if (scrollRaf) return; 448 scrollRaf = window.requestAnimationFrame(function () { 449 scrollRaf = null; 450 updateFixedImgStickyTop(); 451 updateSvgOnScroll(); 452 }); 453 }; 454 455 $(window) 456 .off(scrollNs) 457 .on(scrollNs, onScroll) 458 .off(resizeNs) 459 .on(resizeNs, onResize); 460 461 preloadDesktopMedia(); 462 updateFixedImgStickyTop(); 463 updateSvgOnScroll(); 464 } 465 466 // Fonction pour initialiser sur mobile/tablette (< 1024px) 467 // Simple lazy-loading : les SVG se chargent quand ils entrent dans le viewport 468 function initAccompagnement4WidgetMobile() { 469 if (window.innerWidth >= 1024) { 470 return; 471 } 472 473 const $widget = $(".indy-accompagnement-4-widget"); 474 if (!$widget.length) return; 475 476 const $items = $widget.find(".content-wrapper .item[data-anim]"); 477 if (!$items.length) return; 478 479 // Ãviter de recréer plusieurs observers si Elementor ré-initialise le widget 480 if ($widget.data("indyAcc4MobileInited")) return; 481 $widget.data("indyAcc4MobileInited", true); 482 483 // Fonction pour charger le SVG d'un item (lazy-loading) 484 function loadSvgForItem($item) {
485 const $svgObject = $item.find(".item-img object.svg-animation"); 486 if (!$svgObject.length) return; 487 488 const svgObjectElement = $svgObject[0]; 489 if (!svgObjectElement) return; 490 491 // Vérifier si le SVG est déjà chargé 492 const currentData = svgObjectElement.getAttribute("data"); 493 if (currentData && currentData !== "") { 494 return; // Déjà chargé 495 } 496 497 // Récupérer l'URL depuis l'attribut data-anim de l'item 498 const svgUrl = $item.attr("data-anim"); 499 if (!svgUrl) return; 500 501 // Charger le SVG 502 svgObjectElement.setAttribute("data", svgUrl); 503 const $fallbackImg = $svgObject.find("img"); 504 if ($fallbackImg.length) { 505 $fallbackImg.attr("src", svgUrl); 506 } 507 } 508 509 // Configuration de l'Intersection Observer pour le lazy-loading 510 const observerOptions = { 511 root: null, 512 rootMargin: "-100px 0px", // Attend que l'item soit à 100px dans le viewport avant de charger (ajustable) 513 threshold: 0.2, // Déclenche quand 20% de l'item est visible (ajustable) 514 }; 515 516 // Observer pour charger les SVG au fur et à mesure 517 const loadedItems = new Set(); // Pour éviter de charger plusieurs fois le même item 518 519 const observer = new IntersectionObserver((entries) => { 520 entries.forEach((entry) => { 521 const $item = $(entry.target); 522 const itemKey = entry.target; 523 524 // Si l'item entre dans le viewport et n'a pas encore été chargé 525 if (entry.isIntersecting && !loadedItems.has(itemKey)) { 526 loadedItems.add(itemKey); 527 loadSvgForItem($item); 528 } 529 }); 530 }, observerOptions); 531 532 // Observer tous les items 533 $items.each(function () { 534 observer.observe(this); 535 }); 536 537 // Charger les items déjà visibles au chargement 538 $items.each(function () { 539 const $item = $(this); 540 const rect = this.getBoundingClientRect(); 541 const isVisible = rect.top < window.innerHeight && rect.bottom > 0; 542 543 if (isVisible) { 544 const itemKey = this; 545 if (!loadedItems.has(itemKey)) { 546 loadedItems.add(itemKey); 547 loadSvgForItem($item); 548 } 549 } 550 }); 551 } 552 553 // Fonction principale qui initialise selon la taille d'écran 554 function initAccompagnement4Widget() { 555 // Permet de réinitialiser proprement lors des resize / re-renders 556 $(".indy-accompagnement-4-widget").removeData("indyAcc4MobileInited"); 557 initAccompagnement4WidgetDesktop(); 558 initAccompagnement4WidgetMobile(); 559 } 560 561 // Initialiser quand le DOM est prêt avec jQuery 562 $(document).ready(function () { 563 initAccompagnement4Widget(); 564 }); 565 566 function registerElementorHook() { 567 if (typeof elementorFrontend === "undefined" || !elementorFrontend.hooks) { 568 return; 569 } 570 571 elementorFrontend.hooks.addAction( 572 "frontend/element_ready/indy-accompagnement-4-widget.default", 573 function () { 574 initAccompagnement4Widget(); 575 }, 576 ); 577 } 578 579 // Réinitialiser si Elementor recharge le widget 580 $(window).on("elementor/frontend/init", registerElementorHook); 581 registerElementorHook(); 582 583 // Réinitialiser lors du redimensionnement de la fenêtre 584 let resizeTimeout; 585 $(window).on("resize", function () { 586 clearTimeout(resizeTimeout); 587 resizeTimeout = setTimeout(function () { 588 initAccompagnement4Widget(); 589 }, 250); 590 }); 591})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.