PageSourceSearch

https://baoli-restaurant.com/js/app.js

js baoli-restaurant.com collected 2026-10-04 10:42:23 UTC 21,037 bytes, 882 lines download raw bytes

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.