1const app = { 2 /** 3 * --- Template Options --- 4 */ 5 options: { 6 /** 7 * Header options 8 */ 9 header: { 10 sticky: { 11 toggleAttributes: { 12 'data-arts-header-logo': 'data-arts-header-sticky-logo', 13 'data-arts-color-theme': 'data-arts-header-sticky-color-theme', 14 'class': 'data-arts-header-sticky-class' 15 }, 16 toggleReveal: true, 17 toggleStickyClass: 'header__bar_sticky', 18 toggleRevealingClass: false, 19 toggleScrollingDownClass: 'header__bar_scrolling-down' 20 }, 21 observeHeight: true, 22 matchMediaAutoCloseOverlay: '(min-width: 992px)' 23 }, 24 25 /** 26 * Smooth scrolling options 27 */ 28 smoothScroll: { 29 enabled: true, 30 duration: 1.2, 31 easing: gsap.parseEase('expo.out'), 32 useGSAPRaf: true 33 }, 34 35 /** 36 * Options for components that use virtual scroll 37 */ 38 virtualScroll: { 39 easing: { 40 mouse: 0.1, 41 touch: 0.06 42 }, 43 speed: { 44 mouse: 1, 45 touch: 2.5 46 }, 47 maxDelta: { 48 mouse: 240, 49 touch: 180 50 }, 51 snapDelay: { 52 mouse: 0.05, 53 touch: 0.6 54 } 55 }, 56 57 /** 58 * Loading screen options 59 */ 60 preloader: { 61 timeScale: 1, 62 loadingRotation: 90, 63 loadingSteps: [ 64 [20, 40], 65 [50, 70], 66 [70, 90], 67 [100, 100] 68 ], 69 finalDelay: 0.4, 70 finalOffset: '<20%', 71 finalRotation: 180, 72 toggleLoadClass: 'preloader_loaded', 73 }, 74 75 /** 76 * Mouse cursor follower options 77 */ 78 cursorFollower: { 79 enabled: true, 80 animationDuration: 0.25, 81 hideIFramesHover: true, 82 trailing: 0.2, 83 elastic: 1.5, 84 highlight: { 85 includeClass: 'cursor-highlight', 86 excludeClass: 'cursor-no-highlight', 87 scale: '90px', 88 opacity: .3 89 }, 90 matchMedia: '(hover: hover) and (pointer: fine)', 91 passiveListeners: true 92 }, 93 94 /** 95 * System loading mouse indicator 96 */ 97 cursorLoading: true, 98 99 /** 100 * AJAX navigation options 101 */ 102 ajax: { 103 enabled: true, 104 transitionDuration: 2.0, 105 transitionEase: 'expo.inOut', 106 timeout: 10000, // give max. 10s for a transition to play and force hard-refresh if transition takes longer 107 preventRules: '', // Selectors of the elements to exclude them from AJAX transitions 108 updateNodesAttributes: '', 109 updateScriptNodes: '', 110 loadMissingScripts: true, 111 loadMissingStyles: true, 112 removeMissingStyles: true, 113 evalInlineContainerScripts: false 114 }, 115 116 /** 117 * Photoswipe lightbox 118 */ 119 gallery: { 120 itemsSelector: 'a[href]:not(a[href="#"]):not(a[href*="#"])', 121 bgOpacity: 1.0, 122 colorTheme: 'dark', 123 initialZoomLevel: 'fit', 124 secondaryZoomLevel: 2.5, 125 maxZoomLevel: 4, 126 // "X" (close) button 127 close: { 128 custom: true, 129 label: false, 130 labelHover: false, 131 cursor: { 132 magnetic: 0.25, 133 scale: 1.3, 134 hideNative: false, 135 color: 'var(--color-accent-dark-theme)' 136 } 137 }, 138 // Prev & next gallery arrows 139 arrows: { 140 custom: true, 141 cursor: { 142 scale: 'current', 143 magnetic: 0.25, 144 color: 'var(--color-accent-dark-theme)' 145 } 146 }, 147 // Images counter in gallery (e.g. "2 / 7") 148 counter: { 149 custom: true 150 }, 151 // Images captions grabbed from 'data-caption' attribute on <a> link 152 // or from "alt" attribute of the currently active image 153 captions: true, 154 // Media loading indicator 155 preloader: { 156 custom: true 157 }, 158 // "Zoom" button in top bar 159 zoom: false, 160 }, 161 162 /** 163 * Auto transition to the next portfolio page 164 * at the bottom 165 */ 166 autoScrollNext: { 167 webGL: { 168 enabled: false, 169 vertices: 16, 170 }, 171 onSceneProgress: { 172 speed: 8, 173 amplitude: 4, 174 segments: 4, 175 scalePlane: 1.1, 176 scaleTexture: 1, 177 }, 178 onSceneIdle: { 179 speed: 4, 180 amplitude: 2, 181 segments: 4, 182 scalePlane: 1, 183 scaleTexture: 1.2, 184 }, 185 scrollingClass: 'auto-scroll-next_scrolling', 186 completeClass: 'auto-scroll-next_complete', 187 toggleHeaderVisibility: true 188 }, 189 190 /** 191 * Animations options 192 */ 193 animations: { 194 triggerHook: 0.10, 195 speed: { // slow down or speed up the animations 196 preloader: 1.0, 197 onScrollReveal: 1.0, 198 overlayMenuOpen: 1.0, 199 overlayMenuClose: 1.25, 200 }, 201 curvedMasks: true, 202 curvedMasksForceRepaint: true // fix Safari flickering 203 }, 204 205 /** 206 * SVG shape used for creating "drawing" effect
207 */ 208 circleTemplate: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%"><ellipse cx="50%" cy="50%" rx="48%" ry="48%" fill="none"></svg>` 209 }, 210 211 // Outer content container 212 containerEl: document.querySelector('#page-wrapper'), 213 214 // Inner content container 215 contentEl: document.querySelector('#page-wrapper__content'), 216 217 initAJAX: () => { 218 /** 219 * Le code ici s'exécute au chargement ET à chaque transition de page 220 */ 221 if (typeof app.fixSideMenu === 'function') { 222 app.fixSideMenu(); 223 } 224 }, 225 226 initOnce: () => { 227 /** 228 * The code you put here will be executed only once 229 * on page load. 230 */ 231 232 // On s'assure que Barba est bien défini, puis on ajoute le hook 233 if (typeof barba !== 'undefined') { 234 barba.hooks.before((data) => { 235 const modal = document.getElementById('customModal'); 236 // On cache le pop-up s'il existe avant de changer de page 237 if (modal) { 238 modal.style.display = 'none'; 239 } 240 }); 241 } 242 }, 243 244 init: () => { 245 app.checkIsLocalFile(); 246 247 app.loadPreloader() 248 .then(() => Promise.all([ 249 app.loadScroll(), 250 app.loadHeader() 251 ])) 252 .then(() => app.componentsManager.init({ 253 scope: app.containerEl, 254 loadOnlyFirst: true 255 })[0]) 256 .then(() => Promise.all(app.componentsManager.init({ 257 scope: app.containerEl 258 }))) 259 .then(() => app.utilities.scrollToAnchorFromHash()) 260 .then(() => Promise.all([ 261 app.setLoaded(), 262 app.loadAJAX(), 263 app.loadCursor() 264 ])) 265 .then(() => { 266 app.loadLazy(); 267 app.initOnce(); 268 app.initAJAX(); 269 app.loadGUI(); 270 ScrollTrigger.refresh(); 271 }); 272 }, 273 274 setup: () => { 275 /** 276 * GSAP: turn off console warnings when 277 * attempting to manipulate the null target 278 */ 279 gsap.config({ 280 nullTargetWarn: false 281 }); 282 283 /** 284 * GSAP: register dependant plugins 285 */ 286 gsap.registerPlugin(DrawSVGPlugin); 287 gsap.registerPlugin(ScrollTrigger); 288 gsap.registerPlugin(ScrollToPlugin); 289 gsap.registerPlugin(MorphSVGPlugin); 290 291 /** 292 * Don't recalculate ScrollTrigger instances 293 * when mobile bottom bar is automatically hiding or showing 294 */ 295 ScrollTrigger.config({ 296 ignoreMobileResize: true 297 }); 298 }, 299 300 utilities: new Utilities(), 301 302 animations: new Animations(), 303 304 forms: new Forms(), 305 306 hoverEffect: new HoverEffect(), 307 308 assetsManager: new AssetsManager(), 309 310 componentsManager: new ComponentsManager(), 311 312 lazy: null, 313 314 assets: { 315 'arts-header': [{ 316 type: 'script', 317 src: './js/vendor/arts-header.min.js', 318 id: 'arts-header-js' 319 }], 320 'arts-fullpage-slider': [{ 321 type: 'script', 322 src: './js/vendor/arts-fullpage-slider.min.js', 323 id: 'arts-fullpage-slider-js' 324 }], 325 'arts-infinite-list': [{ 326 type: 'script', 327 src: './js/vendor/arts-infinite-list/arts-infinite-list.min.js', 328 id: 'arts-infinite-list-js' 329 }], 330 'arts-horizontal-scroll': [{ 331 type: 'script', 332 src: './js/vendor/arts-horizontal-scroll.min.js', 333 id: 'arts-horizontal-scroll-js' 334 }], 335 'arts-parallax': [{ 336 type: 'script', 337 src: './js/vendor/arts-parallax.min.js', 338 id: 'arts-parallax-js' 339 }], 340 'arts-cursor-follower': [{ 341 type: 'script', 342 src: './js/vendor/arts-cursor-follower.min.js', 343 id: 'arts-cursor-follower-js' 344 }], 345 'circle-type': [{ 346 type: 'script', 347 src: './js/vendor/circletype.min.js', 348 id: 'circle-type-js' 349 }], 350 'photoswipe': [{ 351 type: 'script', 352 src: './js/vendor/photoswipe.umd.min.js', 353 id: 'photoswipe-js' 354 }, { 355 type: 'script', 356 src: './js/vendor/photoswipe-lightbox.umd.min.js', 357 id: 'photoswipe-lightbox-js' 358 }], 359 'lenis': [{ 360 type: 'script', 361 src: './js/vendor/lenis.min.js', 362 id: 'lenis-js' 363 }], 364 'barba': [{ 365 type: 'script', 366 src: './js/vendor/barba.min.js', 367 id: 'barba-js' 368 }], 369 'curtains': [{ 370 type: 'script', 371 src: './js/vendor/curtains.umd.custom.min.js', 372 id: 'curtains-js' 373 }], 374 'pristine': [{ 375 type: 'script', 376 src: './js/vendor/pristine.min.js', 377 id: 'pristine-js' 378 }], 379 'bootstrap-modal': [{ 380 type: 'script', 381 src: './js/vendor/bootstrap-modal.min.js', 382 id: 'bootstrap-modal-js' 383 }], 384 'isotope': [{ 385 type: 'script', 386 src: './js/vendor/isotope.pkgd.min.js', 387 id: 'isotope-js' 388 }], 389 'dat-gui': [{ 390 type: 'script', 391 src: './js/vendor/dat.gui.min.js', 392 id: 'dat-gui-js' 393 }] 394 }, 395 396 components: { 397 'Preloader': { 398 dependencies: [], 399 file: './components/Preloader.js' 400 }, 401 'Header': { 402 dependencies: ['arts-header'], 403 file: './components/Header.js' 404 }, 405 'MenuOverlay': { 406 dependencies: ['arts-infinite-list'], 407 file: './components/MenuOverlay.js' 408 }, 409 'MenuClassic': { 410 dependencies: [], 411 file: './components/MenuClassic.js' 412 },
413 'SliderFullpageBackgroundsMask': { 414 dependencies: ['arts-fullpage-slider'], 415 files: [{ 416 type: 'script', 417 src: './js/components/SliderFullpageBase.js', 418 id: 'slider-fullpage-base-js' 419 }], 420 file: './components/SliderFullpageBackgroundsMask.js' 421 }, 422 'SliderFullpageBackgroundsSlide': { 423 dependencies: ['arts-fullpage-slider'], 424 files: [{ 425 type: 'script', 426 src: './js/components/SliderFullpageBase.js', 427 id: 'slider-fullpage-base-js' 428 }], 429 file: './components/SliderFullpageBackgroundsSlide.js' 430 }, 431 'SliderTestimonials': { 432 dependencies: ['arts-fullpage-slider'], 433 file: './components/SliderTestimonials.js' 434 }, 435 'InfiniteList': { 436 dependencies: ['arts-infinite-list'], 437 file: './components/InfiniteList.js', 438 }, 439 'CurtainsBase': { 440 dependencies: ['curtains'], 441 file: './components/CurtainsBase.js' 442 }, 443 'SplitCounter': { 444 dependencies: [], 445 file: './components/SplitCounter.js', 446 }, 447 'MarqueeHeader': { 448 dependencies: ['arts-infinite-list'], 449 file: './components/MarqueeHeader.js', 450 }, 451 'MarqueeHeadingsHover': { 452 dependencies: ['arts-infinite-list'], 453 file: './components/MarqueeHeadingsHover.js', 454 }, 455 'ScreensWall': { 456 dependencies: ['arts-infinite-list'], 457 file: './components/ScreensWall.js', 458 }, 459 'RotatingButton': { 460 dependencies: ['circle-type'], 461 file: './components/RotatingButton.js' 462 }, 463 'ArcImages': { 464 dependencies: ['arts-infinite-list'], 465 file: './components/ArcImages.js' 466 }, 467 'Scroll': { 468 dependencies: [], 469 file: './components/Scroll.js' 470 }, 471 'AJAX': { 472 dependencies: ['barba'], 473 file: './components/AJAX.js' 474 }, 475 'Masthead': { 476 dependencies: [], 477 file: './components/Masthead.js' 478 }, 479 'Content': { 480 dependencies: [], 481 file: './components/Content.js' 482 }, 483 'Parallax': { 484 dependencies: ['arts-parallax'], 485 file: './components/Parallax.js' 486 }, 487 'HorizontalScroll': { 488 dependencies: ['arts-horizontal-scroll'], 489 file: './components/HorizontalScroll.js' 490 }, 491 'CursorFollower': { 492 dependencies: ['arts-cursor-follower'], 493 file: './components/CursorFollower.js' 494 }, 495 'PSWP': { 496 dependencies: ['photoswipe'], 497 file: './components/PSWP.js' 498 }, 499 'GMap': { 500 dependencies: [], 501 file: './components/Gmap.js' 502 }, 503 'FormAJAX': { 504 dependencies: ['pristine', 'bootstrap-modal'], 505 file: './components/FormAJAX.js' 506 }, 507 'Grid': { 508 dependencies: ['isotope'], 509 file: './components/Grid.js' 510 }, 511 'AutoScrollNext': { 512 dependencies: [], 513 file: './components/AutoScrollNext.js' 514 }, 515 'FixedHeader': { 516 dependencies: [], 517 file: './components/FixedHeader.js' 518 }, 519 'FixedWall': { 520 dependencies: ['arts-infinite-list'], 521 file: './components/FixedWall.js' 522 }, 523 'CounterUp': { 524 dependencies: [], 525 file: './components/CounterUp.js' 526 }, 527 'SliderImages': { 528 dependencies: ['arts-infinite-list'], 529 file: './components/SliderImages.js' 530 }, 531 'ClickAndHold': { 532 dependencies: [], 533 file: './components/ClickAndHold.js' 534 }, 535 'Mask': { 536 dependencies: [], 537 file: './components/Mask.js' 538 }, 539 'Gui': { 540 dependencies: ['dat-gui', 'barba'], 541 file: './components/Gui.js' 542 }, 543 'MyCustomComponent': { 544 dependencies: [], 545 file: './components/MyCustomComponent.js' 546 } 547 548 }, 549 550 checkIsLocalFile: () => { 551 if (window.location.protocol === 'file:') { 552 const 553 labelElement = document.createElement('div'), 554 errorMessage = 'Please use a web-server to view this page.', 555 helpURL = 'https://docs.artemsemkin.com/asli/html/getting-started/classic-workflow.html', 556 helpLabel = 'Learn More'; 557 558 labelElement.id = 'localFilesystem'; 559 labelElement.className = 'text-center'; 560 labelElement.innerHTML += `<div class="strong mb-4">${errorMessage}</div>`; 561 labelElement.innerHTML += `<a class="button button_solid bg-dark-3 ui-element" href="${helpURL}" target="_blank">${helpLabel}</a>`; 562 563 gsap.set(labelElement, { 564 position: 'fixed', 565 top: '50%', 566 left: '50%', 567 translateX: '-50%', 568 translateY: '-50%', 569 zIndex: 99999, 570 backgroundColor: '#000', 571 color: '#fff', 572 fontSize: 18, 573 padding: '2em' 574 }); 575 576 document.body.append(labelElement); 577 578 throw new Error(label); 579 } 580 }, 581 582 loadGUI: () => { 583 if (!app.shouldLoadGUI()) { 584 return; 585 } 586 587 let el = document.querySelector('.js-gui'); 588 let mq; 589 590 if (!el) { 591 el = document.createElement('div'); 592 el.classList.add('js-gui'); 593 594 document.body.appendChild(el); 595 } 596 597 if (app.shouldLoadGUI()) { 598 mq = window.matchMedia('(min-width: 992px)'); 599 600 if (mq.matches) { 601 return load({ 602 matches: true 603 }); 604 } else { 605 mq.addEventListener('change', load); 606 } 607 } else { 608 return load({ 609 matches: true 610 }); 611 } 612 613 function load(event) { 614 if (event && event.matches) { 615 if (mq) { 616 mq.removeEventListener('change', this); 617 } 618 619 return app.componentsManager.loadComponent({ 620 el, 621 loadInnerComponents: false, 622 parent: null, 623 storage: app.componentsManager.instances.persistent, 624 name: 'Gui', 625 options: app.options.gui, 626 }); 627 } 628 } 629 }, 630 631 shouldLoadGUI: () => { 632 const currentURL = new URL(window.location.href); 633 634 return currentURL.searchParams.has('gui') && currentURL.searchParams.get('gui') !== 'false'; 635 }, 636 637 loadLazy: () => { 638 return new Promise((resolve) => {
639 app.lazy = new LazyLoad({ 640 threshold: 800, 641 cancel_on_exit: false, 642 unobserve_entered: true 643 }); 644 645 resolve(true); 646 }); 647 }, 648 649 loadScroll: (resetPosition = true) => { 650 if (app.shouldLoadSmoothScroll()) { 651 app.components.Scroll.dependencies.push('lenis'); 652 } 653 654 return new Promise((resolve) => { 655 app.componentsManager.loadComponent({ 656 el: app.containerEl, 657 loadInnerComponents: false, 658 parent: null, 659 storage: app.componentsManager.instances.persistent, 660 name: 'Scroll', 661 options: app.options.smoothScroll, 662 }).then(resetPosition ? () => app.utilities.scrollTo({ 663 target: 0, 664 delay: 0, 665 duration: 0.05 666 }) : null).then(() => resolve(true)); 667 }); 668 }, 669 670 shouldLoadSmoothScroll() { 671 return ScrollTrigger.isTouch !== 1 && app.utilities.isEnabledOption(app.options.smoothScroll); 672 }, 673 674 loadAJAX() { 675 if (app.shoudLoadAJAX()) { 676 const el = document.querySelector('[data-barba="wrapper"]'); 677 678 // Map "cursorLoading" option 679 if (!!app.options.cursorLoading && !app.options.ajax.cursorLoading) { 680 app.options.ajax.cursorLoading = app.options.cursorLoading; 681 } 682 683 return app.componentsManager.loadComponent({ 684 el, 685 loadInnerComponents: false, 686 parent: null, 687 storage: app.componentsManager.instances.persistent, 688 name: 'AJAX', 689 options: app.options.ajax, 690 }); 691 } 692 }, 693 694 shoudLoadAJAX() { 695 return app.utilities.isEnabledOption(app.options.ajax); 696 }, 697 698 loadHeader: () => { 699 const el = document.querySelector('#page-header'); 700 701 return app.componentsManager.loadComponent({ 702 el, 703 loadInnerComponents: true, 704 storage: app.componentsManager.instances.persistent, 705 parent: null, 706 options: app.options.header 707 }); 708 }, 709 710 loadCursor: () => { 711 const el = document.querySelector('[data-arts-cursor-follower="cursor"]'); 712 let mq; 713 714 if (app.shouldNotLoadCursor()) { 715 document.documentElement.classList.add('no-cursor-follower'); 716 return; 717 } 718 719 if (app.shouldLoadCursor()) { 720 mq = window.matchMedia(app.options.cursorFollower.matchMedia); 721 722 if (mq.matches) { 723 return load({ 724 matches: true 725 }); 726 } else { 727 document.documentElement.classList.add('no-cursor-follower'); 728 mq.addEventListener('change', load); 729 } 730 } else { 731 return load({ 732 matches: true 733 }); 734 } 735 736 function load(event) { 737 if (event && event.matches) { 738 if (mq) { 739 mq.removeEventListener('change', this); 740 } 741 742 return app.componentsManager.loadComponent({ 743 el, 744 loadInnerComponents: false, 745 parent: null, 746 storage: app.componentsManager.instances.persistent, 747 name: 'CursorFollower', 748 options: app.options.cursorFollower, 749 }); 750 } 751 } 752 }, 753 754 shouldNotLoadCursor() { 755 return !app.options.cursorFollower || (!!app.options.cursorFollower && !app.options.cursorFollower.enabled); 756 }, 757 758 shouldLoadCursor() { 759 return app.utilities.isEnabledOption(app.options.cursorFollower) && !!app.options.cursorFollower.matchMedia; 760 }, 761 762 loadPreloader() { 763 return new Promise((resolve) => { 764 const el = document.querySelector('#js-preloader'); 765 766 if (el) { 767 if (app.shouldLoadPreloader()) { 768 app.componentsManager.loadComponent({ 769 el, 770 loadInnerComponents: true, 771 parent: null, 772 storage: app.componentsManager.instances.persistent, 773 options: app.options.preloader, 774 }).then(() => resolve(true)); 775 } else { 776 el.style.display = 'none'; 777 778 resolve(true); 779 } 780 } else { 781 resolve(true); 782 } 783 }); 784 }, 785 786 shouldLoadPreloader() { 787 return app.utilities.isEnabledOption(app.options.preloader); 788 }, 789 790 setLoaded: () => { }, 791}; 792 793app.loaded = new Promise((resolve) => { 794 app.setLoaded = resolve; 795}); 796app.setup(); 797app.init(); 798 799// On définit une fonction réutilisable pour réparer les menus 800app.fixSideMenu = function() { 801 802 // --- PARTIE 1 : GESTION DES SOUS-MENUS (Dining / Pool & Beach) --- 803 const menuTriggers = document.querySelectorAll('.menu-trigger'); 804 805 if (menuTriggers.length > 0) { 806 // On clone les éléments pour éviter d'empiler les événements si la fonction est lancée 2 fois 807 menuTriggers.forEach(trigger => { 808 const newTrigger = trigger.cloneNode(true); 809 trigger.parentNode.replaceChild(newTrigger, trigger); 810 }); 811 812 // On ré-attache les événements sur les nouveaux éléments 813 const cleanTriggers = document.querySelectorAll('.menu-trigger');
814 cleanTriggers.forEach(trigger => { 815 ['click', 'touchend'].forEach(eventType => { 816 trigger.addEventListener(eventType, function(e) { 817 if (e.cancelable) e.preventDefault(); 818 e.stopPropagation(); 819 820 const parent = this.closest('.has-submenu'); 821 const otherMenus = document.querySelectorAll('.has-submenu.active'); 822 823 otherMenus.forEach(menu => { 824 if (menu !== parent) menu.classList.remove('active'); 825 }); 826 827 parent.classList.toggle('active'); 828 }, { passive: false }); 829 }); 830 }); 831 } 832 833 // Fermeture au clic en dehors 834 document.addEventListener('click', function(e) { 835 if (!e.target.closest('.has-submenu')) { 836 document.querySelectorAll('.has-submenu.active').forEach(m => m.classList.remove('active')); 837 } 838 }); 839 840 // --- PARTIE 2 : GESTION DU BOUTON RESERVATIONS (OUVERTURE/FERMETURE) --- 841 const menuToggle = document.getElementById('menu-toggle'); 842 const menuModal = document.getElementById('menu'); 843 844 if (menuToggle && menuModal) { 845 // Nettoyage des anciens événements (clone) pour éviter les doublons 846 const newToggle = menuToggle.cloneNode(true); 847 menuToggle.parentNode.replaceChild(newToggle, menuToggle); 848 849 newToggle.addEventListener('click', function(e) { 850 e.preventDefault(); 851 menuModal.classList.add('active'); 852 document.body.style.overflow = 'hidden'; 853 }); 854 855 const closeBtn = menuModal.querySelector('.modal-close'); 856 if (closeBtn) { 857 // On clone aussi le bouton fermer pour éviter les doublons 858 const newClose = closeBtn.cloneNode(true); 859 closeBtn.parentNode.replaceChild(newClose, closeBtn); 860 861 newClose.addEventListener('click', function(e) { 862 e.preventDefault(); 863 menuModal.classList.remove('active'); 864 document.body.style.overflow = ''; 865 }); 866 } 867 } 868 869 // --- PARTIE 3 : FIX MOBILE LINKS (Anti double-tap zoom) --- 870 const menuLinks = document.querySelectorAll('.menu-links a'); 871 if (menuLinks.length > 0) { 872 menuLinks.forEach(link => { 873 link.addEventListener('touchend', function(e) { 874 // Si le lien a une URL de booking spécifique 875 if (this.getAttribute('data-booking-url')) { 876 e.preventDefault(); 877 window.location.href = this.getAttribute('data-booking-url'); 878 } 879 }); 880 }); 881 } 882};
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.