PageSourceSearch

https://www.sacrefrancais.fr/js/active.js?v=2026101

js sacrefrancais.fr collected 2026-10-05 05:19:31 UTC 119,237 bytes, 3,271 lines download raw bytes

1/*********************************************************************************
2
3	Template Name: 	Sacré Français 2022
4	Template URI: 	https://SacreFrancais.fr
5	Description: 	Thème graphique du site Sacré Français - Sélectionneur d'épices
6	Author: 		La chose verte
7	Author URI: 	https://lachoseverte.com
8	Version: 		2.0
9
10**********************************************************************************/
11
12(function($)
13{
14	'use strict';
15
16////////////////////////////////////////////////////////////////////////////////
17///////	///////	///////	///////	///////	///////	///////	///////	////////
18////////////////////////////////////////////////////////////////////////////////
19
20	jQuery.expr[':'].icontains = function(a, i, m)
21	{
22		return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0;
23	};
24
25////////////////////////////////////////////////////////////////////////////////
26///////	///////	///////	///////	///////	///////	///////	///////	////////
27////////////////////////////////////////////////////////////////////////////////
28
29	$.fn.hasScrollBar = function()
30	{
31		return Math.round(this.get(0).scrollHeight) > Math.round(this.outerHeight());
32	}
33
34////////////////////////////////////////////////////////////////////////////////
35///////	///////	///////	///////	///////	///////	///////	///////	////////
36////////////////////////////////////////////////////////////////////////////////
37
38async function CheckCartBeforeCheckout() {
39	let updated = false;
40	const state = Snipcart.store.getState();
41	const cart = state?.cart;
42
43	if (!cart?.items || !cart.items.items.length) return;
44
45	const Datas = [];
46	for (const item of cart.items.items) {
47		const packField = item.customFields?.find(f => f.name === 'Conditionnement');
48		const weightField = item.customFields?.find(f => f.name === 'Poids');
49		const pack = packField ? (packField.displayValue || packField.value) : null;
50
51		if (!pack) continue;
52
53		Datas.push({
54			id:        item.id,
55			pack:      pack,
56			price:     Number.parseFloat(item.price),
57			weight:    Number.parseFloat(weightField?.value || 0),
58			uniqueId:  item.uniqueId,
59			Quantity:  item.quantity,
60			Name:      item.name
61		});
62	}
63
64	if (Datas.length === 0) return;
65
66	const removedNames = [];
67
68	$.ajax({
69		type: 'POST',
70		url: RootPath() + '/Ajax.php',
71		dataType: 'json',
72		data: {
73			action: 'CheckCartProducts',
74			Datas: JSON.stringify(Datas)
75		},
76		cache: false,
77		success: async function (json) {
78			if (!json || !json.results || !Array.isArray(json.results)) return;
79
80			for (const result of json.results) {
81				const Available = result.available;
82				const Status    = result.status;
83				const UniqueId  = result.uniqueId;
84				const Name      = result.name;
85				const NewPrice  = result.newPrice;
86				const NewWeight = result.newWeight;
87
88				// Normalise (évite NaN)
89				const priceNum  = (NewPrice  !== undefined && NewPrice  !== null) ? Number(NewPrice)  : undefined;
90				const weightNum = (NewWeight !== undefined && NewWeight !== null) ? Number(NewWeight) : undefined;
91
92				// --- Retrait ---
93				if (Status === 'remove' || Available === false) {
94					try {
95						await Snipcart.api.cart.items.remove(UniqueId);
96						removedNames.push(Name);
97						updated = true;
98					} catch (error) {
99						console.error('❌ Erreur Snipcart remove', UniqueId, error);
100					}
101					continue;
102				}
103
104				// --- Mise à jour ---
105				if (Status === 'update') {
106					try {
107						const current = Snipcart.store.getState()?.cart?.items?.items?.find(i => i.uniqueId === UniqueId);
108						if (!current) {
109							// Item plus présent (course/refresh) → on ignore cet item et on continue
110							continue;
111						}
112
113						// Clone & met à jour le champ "Poids" si fourni
114						let updatedFields = current.customFields || [];
115						if (Number.isFinite(weightNum)) {
116							updatedFields = updatedFields.map(f =>
117								f.name === 'Poids' ? { ...f, value: weightNum } : f
118							);
119						}
120
121						const updatePayload = { uniqueId: UniqueId };
122						if (Number.isFinite(priceNum))  updatePayload.price = priceNum;
123						if (updatedFields)              updatePayload.customFields = updatedFields;
124
125						await Snipcart.api.cart.items.update(updatePayload);
126						updated = true;
127					} catch (error) {
128						console.error(`❌ Erreur Snipcart update ${UniqueId} :`, error);
129					}
130					continue;
131				}
132			}
133
134			// ——— Message de retrait (si nécessaire) ———
135			if (removedNames.length > 0) {
136				if (removedNames.length === 1) {
137					const safeName = $('<div>').text(removedNames[0]).html();
138					ShowCartMessage(
139						`L’article <strong>${safeName}</strong> a été retiré de votre panier car il n’est plus disponible.`,
140						'warning'
141					);
142				} else {
143					const safeList = removedNames.map(n =>
143 $('<div>').text(n).html()).join(', ');
144					ShowCartMessage(
145						`Les articles <strong>${safeList}</strong> ont été retirés de votre panier car ils ne sont plus disponibles.`,
146						'warning'
147					);
148				}
149			}
150		},
151		error: function (xhr, status, error) {
152			console.error('❌ Erreur AJAX :', error, xhr?.responseText);
153		}
154	});
155}
156
157/*
158	async function CheckCartBeforeCheckout()
159	{
160		let updated 	= false;
161		const cart 		= Snipcart.store.getState().cart;
162		if(!cart.items || !cart.items.items.length)
163		{
164			return;
165		}
166		let Datas 		= [];
167		const nameByUniqueId = new Map(); // <- pour reconstruire les noms après remove
168		for(const item of cart.items.items)
169		{
170			const productId 	= item.id;
171			const packField 	= item.customFields?.find(f => f.name === 'Conditionnement');
172			const pack 			= packField ? (packField.displayValue || packField.value) : null;
173	
174			if(!pack) continue;
175	
176			Datas.push({
177				id: 		productId,
178				pack: 		pack,
179				price: 		parseFloat(item.price),
180				weight: 	parseFloat(item.customFields?.find(f => f.name === 'Poids')?.value || 0),
181				uniqueId: 	item.uniqueId,
182				Quantity:	item.quantity,
183				Name:		item.name
184			});
185		}
186//		console.log(Datas);
187		if(Datas.length === 0) return;
188		const removedNames = []; // on accumule pour le message
189		$.ajax({
190			type: 'POST', url: RootPath()+'/Ajax.php', dataType: 'json',
191			data: {
192				action: 'CheckCartProducts',
193				Datas: JSON.stringify(Datas)
194			},
195			cache: false,
196			success: async function(json)
197			{
198				if(!json || !json.results) return;
199				for(const result of json.results)
200				{
201					const Available 	= result.available;
202					const Status 		= result.status;
203					const UniqueId 		= result.uniqueId;
204					const ProductName 	= result.name;
205					const NewPrice 		= result.newPrice;
206					const NewWeight 	= result.newWeight;
207
208					if(Status === 'remove' || Available === false)
209					{
210						try {
211							await Snipcart.api.cart.items.remove(UniqueId);
212//							const name = nameByUniqueId.get(UniqueId) || 'Article supprimé';
213							removedNames.push(ProductName);
214						} catch (error) {
215							console.log(error)
216						}
217						updated = true;
218						continue;
219					}
220					if(Status === 'update')
221					{
222//						console.log({ Status, NewPrice, NewWeight, Available, UniqueId });
223//						console.log(Snipcart.store.getState().cart);
224						try {
225							// Récupère l'item complet dans le panier actuel
226							const cart = Snipcart.store.getState().cart;
227							const item = cart.items.items.find(i => i.uniqueId === UniqueId);
228						
229							if(!item) {
230//								console.warn(`Item ${UniqueId} introuvable dans le panier`);
231								return;
232							}
233						
234							// Clone et modifie le champ "Poids" sans détruire les autres
235							const updatedFields = item.customFields.map(f =>
236								f.name === 'Poids'
237									? { ...f, value: NewWeight } // conserve toutes les propriétés originales (type, operation, etc.)
238									: f
239							);
240						
241							await Snipcart.api.cart.items.update({
242								uniqueId: UniqueId,
243								price: NewPrice,
244								customFields: updatedFields
245							});
246						
247//							console.log(`✅ Item ${UniqueId} mis à jour`, { NewPrice, NewWeight });
248						
249						} catch (error) {
250							console.error(`❌ Erreur Snipcart sur update ${UniqueId} :`, error);
251						}
252						updated = true;
253					}
254				}
255				// ——— Afficher le message si des articles ont été retirés ———
256				if(removedNames.length > 0)
257				{
258					const list = removedNames
259						.map(n => `<li class="mb-0">- ${$('<div>').text(n).html()}</li>`)
260						.join('');
261				
262					let html = '';
263				
264					if (removedNames.length > 0) {
265						let html = '';
266					
267						if (removedNames.length === 1) {
268							html = `L’article <strong>${$('<div>').text(removedNames[0]).html()}</strong> a été retiré de votre panier car il n’est plus disponible.`;
269						} else {
270							const names = removedNames.map(n => $('<div>').text(n).html()).join(', ');
271							html = `Les articles <strong>${names}</strong> ont été retirés de votre panier car ils ne sont plus disponibles.`;
272						}
273					
274						ShowCartMessage(html, 'warning');
275					}
276				}
277			},
278			error: function (xhr, status, error)
279			{
280				console.error("❌ Erreur AJAX :", error, xhr.responseText);
281			}
282		});
283	}
284*/
285
286////////////////////////////////////////////////////////////////////////////////
287///////	///////	///////	///////	///////	///////	///////	///////	////////
288////////////////////////////////////////////////////////////////////////////////
289
290function ShowCartMessage(html, type = 'warning') {
291	// Si un conteneur dédié existe, on l’utilise, sinon on ajoute en haut du body
292	const $host = $('#SnipcartMessages');
293	const $container = $host.length ? $host : $('body');
294
295	// Supprime une alerte précédente du même type pour éviter l’empilement
296	$container.find('.cart-alert').remove();
297
298	const $alert = $(`
299		<div class="col-12 col-md-3 d-flex cart-alert alert alert-${type} Bg-Orange text-white position-fixed align-items-center fade show"
300			 role="alert"
301			 style="z-index: 1060; ${$host.length ? '' : 'top:5vh; left:50%; transform:translateX(-50%);'}">
302			<div class="d-flex align-items-start gap-2">
303				<i class="bi bi-exclamation-triangle-fill me-1"></i>
304				<div class="cart-alert-body">${html}</div>
305			</div>
306			<button type="button" class="h-100 btn-close text-white p-0 fs-3 ps-4" data-bs-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
307		</div>
308	`);
309	$container.append($alert);
310
311	// Fallback si Bootstrap n'est pas chargé : auto-hide
312	if (typeof bootstrap === 'undefined') {
313		setTimeout(() => $alert.fadeOut(200, () => $alert.remove()), 6000);
314	}
315}
316
317////////////////////////////////////////////////////////////////////////////////
318///////	///////	///////	///////	///////	///////	///////	///////	////////
319////////////////////////////////////////////////////////////////////////////////
320
321	function RotateHomeSquares() {
322		const $main = $('#MainRotatingCard');
323		const $cards = $('#SideRotatingCards .card');
324		// Récupère l'URL du background-image
325		const getBgUrl = $el => {
326			const bg = $el.css('background-image');
327			return bg.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
328		};
329
330		const setBgUrl = ($el, url) => {
331			$el.css('background-image', `url("${url}")`);
332		};
333
334		// Stocke les données du main
335		const mainData = {
336			img: getBgUrl($main.find('.BgImg')),
337			title: $main.find('.card-title').html(),
338			desc: $main.find('.card-desc').html(),
339			link: $main.find('a.card-link').attr('href')
340		};
341
342		// Récupère la dernière tuile
343		const $lastCard = $cards.last();
344		const lastCardData = {
345			img: getBgUrl($lastCard.find('.BgImg')),
346			title: $lastCard.find('.card-title').html(),
347			desc: $lastCard.find('.card-desc').html(),
348			link: $lastCard.find('a.card-link').attr('href')
349		};
350
351		// Décale les données des cartes
352		for(let i = $cards.length - 1; i > 0; i--) {
353			const $current = $cards.eq(i);
354			const $prev = $cards.eq(i - 1);
355
356			setBgUrl($current.find('.BgImg'), getBgUrl($prev.find('.BgImg')));
357			$current.find('.card-title').html($prev.find('.card-title').html());
358			$current.find('.card-desc').html($prev.find('.card-desc').html());
359
360			const $link = $current.find('a.card-link');
361			$link.attr('href', $prev.find('a.card-link').attr('href'));
362			adjustLinkAttributes($link);
363		}
364
365		// Le main devient la première carte
366		const $firstCard = $cards.first();
367		setBgUrl($firstCard.find('.BgImg'), mainData.img);
368		$firstCard.find('.card-title').html(mainData.title);
369		$firstCard.find('.card-desc').html(mainData.desc);
370		const $firstLink = $firstCard.find('a.card-link');
371		$firstLink.attr('href', mainData.link);
372		adjustLinkAttributes($firstLink);
373
374		// La dernière carte devient le main
375		setBgUrl($main.find('.BgImg'), lastCardData.img);
376		$main.find('.card-title').html(lastCardData.title);
377		$main.find('.card-desc').html(lastCardData.desc);
378		const $mainLink = $main.find('a.card-link');
379		$mainLink.attr('href', lastCardData.link);
380		adjustLinkAttributes($mainLink);
381	}
382
383////////////////////////////////////////////////////////////////////////////////
384///////	///////	///////	///////	///////	///////	///////	///////	////////
385////////////////////////////////////////////////////////////////////////////////
386
387	function adjustLinkAttributes($a) {
388		const href = $a.attr('href');
389		if(href && href.startsWith('#')) {
390			$a.attr('data-bs-toggle', 'modal');
391		} else {
392			$a.removeAttr('data-bs-toggle');
393		}
394	}
395
396////////////////////////////////////////////////////////////////////////////////
397///////	///////	///////	///////	///////	///////	///////	///////	////////
398////////////////////////////////////////////////////////////////////////////////
399
400	function RemoveHandSwipeImg(That)
401	{
402		$(That).on('changed.owl.carousel', function(e)
403		{
404			$(That).find('.owl-nav').addClass('Slided');
405
406			$('.row.SF_SingleTab').each(function()
407			{
408				var $LastSlide 		= $(this).find('.owl-stage-outer .owl-item:last').prev('.owl-item').find('.ProductThumb'),
409					$LastCatLink	= $(this).find('.LastCatLink'),
410					LastHeight 		= parseInt($LastSlide.height(),10),
411					PaddingTop 		= (LastHeight - parseInt($LastCatLink.height(),10))/2;
412				$LastCatLink.css('padding-top', PaddingTop+'px');
413				$(this).find('.CatLinkInSlide .CatLink').css('height', LastHeight+'px');
414			});
415		});
416	}
417
418////////////////////////////////////////////////////////////////////////////////
419///////	///////	///////	///////	///////	///////	///////	///////	////////
420////////////////////////////////////////////////////////////////////////////////
421
422	function StickyPaymentStepsButtons(Origin)
423	{
424		var intervalId;
425		function applyStickyCss()
426		{
427			var $SubmitButton	= $('#snipcart[data-snipcart-page="/checkout"]').find('button.snipcart-button-primary.snipcart-submit');
428			var $CartSummary	= $('#snipcart[data-snipcart-page="/checkout"] .snipcart-cart-summary');
429			var WindowHeight	= $(window).height();
430
431			if($SubmitButton.length && $CartSummary.length)
432			{
433				var CartSummaryRect		= $CartSummary[0].getBoundingClientRect(),
434					TopButton			= parseInt(CartSummaryRect.top+CartSummaryRect.height+32,10);
435				if(CartSummaryRect && WindowHeight > TopButton+32 /* && !$EditingOverLay.length */)
436				{
437					$SubmitButton.css({
438						'position':'fixed',
439						'top':TopButton+'px',
440						'left':parseInt(CartSummaryRect.left,10)+'px',
441						'width':parseInt(CartSummaryRect.width,10)+'px',
442						'z-index':10,
443						'opacity':1
444					});
445					clearInterval(intervalId);
446				}
447				else
448				{
449					resetCss();
450				}
451			}
452		}
453		function resetCss()
454		{
455			var $SubmitButton	= $('#snipcart[data-snipcart-page="/checkout"]').find('button.snipcart-button-primary.snipcart-submit');
456			if($SubmitButton.length)
457			{
458				$SubmitButton.css({
459					'position':'',
460					'top':'',
461					'left':'',
462					'width':'100%',
463					'z-index':'',
464					'opacity':'1'
465				});
466			}
467		}
468		function startInterval()
469		{
470			if(intervalId)
471			{
472				clearInterval(intervalId);
473			}
474			intervalId	= setInterval(function()
475			{
476				if(window.innerWidth >= 1024)
477				{
478					applyStickyCss();
479				}
480				else
481				{
482					resetCss();
483				}
484			},500);
485		}
486		var observer	= new MutationObserver(function(mutationsList)
487		{
488			if(window.innerWidth >= 1024)
489			{
490				startInterval();
491			}
492		});
493
494		var $TargetNode	= document.querySelector('#snipcart[data-snipcart-page="/checkout"]');
495		if($TargetNode)
496		{
497			observer.observe($TargetNode,{childList:true,subtree:true,attributes:true});
498			if(window.innerWidth >= 1024)
499			{
500				startInterval();
501			}
502		}
503
504		window.addEventListener('resize',function()
505		{
506			if(window.innerWidth >= 1024)
507			{
508				startInterval();
509			}
510			else
511			{
512				resetCss();
513				clearInterval(intervalId);
514			}
515		});
516	}
517
518////////////////////////////////////////////////////////////////////////////////
519///////	///////	///////	///////	///////	///////	///////	///////	////////
520////////////////////////////////////////////////////////////////////////////////
521
522	function HTML_Reviews(Review)
523	{
524		let DateReview = new Date(Review.date), 
525			HTML	= '<li id="Slide'+Review.id+'" class="splide__slide p-2 mt-3 d-flex">'+
526							'<a href="https://www.societe-des-avis-garantis.fr/sacre-francais/" target="_blank">'+
527								'<div class="review-top d-flex">'+
528									'<div class="wrapper_review-picto">'+
529										'<img src="'+RootPath()+'/images/svg/cocarde.svg" alt="Société des avis garantis" class="review-picto">'+
530									'</div>'+
531									'<span class="Rating Rate'+Review.r+'">'+
532										'<label></label><label></label><label></label><label></label><label></label>'+
533									'</span>'+
534								'</div>'+
535								'<div class="review-text">'+
536									'<p>'+Review.txt+'</p>'+
537								'</div>'+
538								'<div class="author">'+
539									'<div class="author-picto">'+
540										'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><circle fill="none" cx="12" cy="7" r="3"></circle><path fill="white" d="M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5S14.757 2 12 2zM12 10c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3S13.654 10 12 10zM21 21v-1c0-3.859-3.141-7-7-7h-4c-3.86 0-7 3.141-7 7v1h2v-1c0-2.757 2.243-5 5-5h4c2.757 0 5 2.243 5 5v1H21z"></path></svg>'+
541									'</div>'+
542									'<div class="author-infos d-flex">'+
543										'<span class="author-pseudo">'+Review.c+'</span>'+
544										'<span class="author-time">'+
545											'<span class="author-date">'+
546												'<span id="datetime">
546Le '+DateReview.toLocaleString()+'</span>'+
547											'</span>'+
548										'</span>'+
549									'</div>'+
550									'<div class="author-authenticated">'+
551										'<svg  alt="Acheteur certifié" class="" data-content="Acheteur authentifié" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#5bc200" d="M11.488,21.754c0.294,0.157,0.663,0.156,0.957-0.001c8.012-4.304,8.581-12.713,8.574-15.104 c-0.001-0.394-0.235-0.744-0.596-0.903L12.373,2.18c-0.259-0.114-0.554-0.114-0.813,0.001L3.566,5.747 C3.211,5.906,2.979,6.25,2.974,6.639C2.94,9.018,3.419,17.445,11.488,21.754z M8.674,10.293l2.293,2.293l4.293-4.293l1.414,1.414 l-5.707,5.707L7.26,11.707L8.674,10.293z"></path></svg>'+
552										'<span>Acheteur authentifié</span>'+
553										'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="svg-quote">'+
554											'<defs>'+
555												'<linearGradient id="gradient" gradientTransform="rotate(-20)">'+
556													'<stop offset="0%" stop-color="#0e559f"></stop>'+
557													'<stop offset="100%" stop-color="#e53434"></stop>'+
558												'</linearGradient>'+
559											'</defs>'+
560											'<path fill="url(#gradient)" d="M20.309 17.708C22.196 15.66 22.006 13.03 22 13v-3V5c0-.552-.447-1-1-1h-6c-1.103 0-2 .897-2 2v7c0 .552.447 1 1 1h1 2.078c-.021.402-.123.912-.429 1.396-.508.801-1.465 1.348-2.846 1.624L13 17.18V20h1C16.783 20 18.906 19.229 20.309 17.708zM9.302 17.708c1.888-2.048 1.697-4.678 1.691-4.708v-3V5c0-.552-.447-1-1-1h-6c-1.103 0-2 .897-2 2v7c0 .552.447 1 1 1h1 2.078c-.021.402-.123.912-.429 1.396-.508.801-1.465 1.348-2.846 1.624L1.993 17.18V20h1C5.776 20 7.899 19.229 9.302 17.708z"></path>'+
561										'</svg>'+
562									'</div>'+
563								'</div>'+
564							'</a>'+
565						'</li>';
566		return HTML;
567	}
568
569////////////////////////////////////////////////////////////////////////////////
570///////	///////	///////	///////	///////	///////	///////	///////	////////
571////////////////////////////////////////////////////////////////////////////////
572
573	function HTML_StarNote(Note)
574	{
575		return '<svg version="1.1" class="StarMask mx-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 493.5 100" xml:space="preserve"><rect y="1.8" class="StarRateFull" width="490.1" height="95.7"/><rect y="1.8" class="StarRate" width="'+(100/5*Note)+'%" height="95.7"/> <g> <path class="st2" d="M0,0v100h493.5V0H0z M95.1,41.5l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8 L52.5,78.7c-0.8-0.4-1.7-0.4-2.5,0L26.6,91c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3L7.7,41.3 c-1.6-1.5-0.7-4.2,1.5-4.5L35.4,33c0.9-0.1,1.6-0.7,2-1.4L49.1,7.9c1-2,3.8-2,4.7,0l11.6,23.7c0.4,0.8,1.1,1.3,2,1.5L93.7,37 C95.8,37.3,96.7,40,95.1,41.5z M192.9,41.5l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8l-23.4-12.4 c-0.8-0.4-1.7-0.4-2.5,0L124.4,91c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3l-18.9-18.5c-1.6-1.5-0.7-4.2,1.5-4.5 l26.2-3.7c0.9-0.1,1.6-0.7,2-1.4l11.8-23.7c1-2,3.8-2,4.7,0l11.6,23.7c0.4,0.8,1.1,1.3,2,1.5l26.2,3.9 C193.7,37.3,194.5,40,192.9,41.5z M290.8,41.5l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8l-23.4-12.4 c-0.8-0.4-1.7-0.4-2.5,0L222.3,91c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3l-18.9-18.5c-1.6-1.5-0.7-4.2,1.5-4.5 L231,33c0.9-0.1,1.6-0.7,2-1.4l11.8-23.7c1-2,3.8-2,4.7,0l11.6,23.7c0.4,0.8,1.1,1.3,2,1.5l26.2,3.9 C291.5,37.3,292.3,40,290.8,41.5z M388.6,41.5l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8L346,78.7 c-0.8-0.4-1.7-0.4-2.5,0L320.1,91c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3l-18.9-18.5c-1.6-1.5-0.7-4.2,1.5-4.5 l26.2-3.7c0.9-0.1,1.6-0.7,2-1.4l11.8-23.7c1-2,3.8-2,4.7,0L359,31.7c0.4,0.8,1.1,1.3,2,1.5l26.2,3.9 C389.3,37.3,390.2,40,388.6,41.5z M486.4,41.5l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8l-23.4-12.4 c-0.8-0.4-1.7-0.4-2.5,0L417.9,91c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3L399,41.3c-1.6-1.5-0.7-4.2,1.5-4.5 l26.2-3.7c0.9-0.1,1.6-0.7,2-1.4l11.8-23.7c1-2,3.8-2,4.7,0l11.6,23.7c0.4,0.8,1.1,1.3,2,1.5L485,37C487.1,37.3,488,40,486.4,41.5z "/></g></svg>';
576	}
577
578////////////////////////////////////////////////////////////////////////////////
579///////	///////	///////	///////	///////	///////	///////	///////	////////
580////////////////////////////////////////////////////////////////////////////////
581
582	function HTML_StarNoteSimplified(Note)
583	{
584		let HTML 		= '<div class="StarNote Rate'+Note+'">';
585
586		for(let i = 0; i < 5; i++)
587		{
588			HTML += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 89 85" xml:space="preserve"><g><path class="st0" d="M88.2,35.1l-19,18.4c-0.6,0.6-0.9,1.5-0.8,2.3l4.4,26.1c0.4,2.2-1.9,3.8-3.8,2.8L45.6,72.3 c-0.8-0.4-1.7-0.4-2.5,0L19.7,84.6c-1.9,1-4.2-0.6-3.8-2.8l4.5-26.1c0.1-0.9-0.1-1.7-0.8-2.3L0.8,34.9c-1.6-1.5-0.7-4.2,1.5-4.5 l26.2-3.8c0.9-0.1,1.6-0.7,2-1.4L42.2,1.5c1-2,3.8-2,4.7,0l11.6,23.7c0.4,0.8,1.1,1.3,2,1.5l26.3,3.9 C88.9,30.9,89.8,33.6,88.2,35.1z"/></g></svg>';
589		}
590		HTML += '</div>';
591		return HTML;
592	}
593
594////////////////////////////////////////////////////////////////////////////////
595///////	///////	///////	///////	///////	///////	///////	///////	////////
596////////////////////////////////////////////////////////////////////////////////
597
598	function GetAsideContent() 
599	{
600		$(document).on('show.bs.offcanvas', '#SF_Aside', function (e) 
601		{
602			const $trigger = $(e.relatedTarget); // <a> qui a déclenché l'ouverture
603			if(!$trigger.length) return;
604	
605			const action = $trigger.data('aside-action') || '';
606			const type   = $trigger.data('aside-type')   || '';
607			const $aside = $('#SF_Aside .offcanvas-body');
608	
609			// Nettoie le contenu avant chargement
610			$aside.html('<div class="text-center p-5"><div class="spinner-border text-secondary" role="status"></div></div>');
611	
612			// Vérifie les données avant d’appeler Ajax
613			if(IsEmpty(action) || IsEmpty(type)) 
614			{
615				$aside.html('<div class="alert alert-warning m-3">Données manquantes.</div>');
616				return;
617			}
618	
619			$.ajax({
620				type: 'POST',
621				url: RootPath() + '/Ajax.php',
622				dataType: 'json',
623				data: {
624					action: action,
625					CookingType: type
626				},
627				cache: false,
628				success: function (res)
629				{
630					if(res && res.html)
631					{
632						$aside.html(res.html);
633					}
634					else if(res && res.error)
635					{
636						$aside.html('<div class="alert alert-danger m-3">' + res.error + '</div>');
637					}
638					else
639					{
640						$aside.html('<div class="alert alert-secondary m-3">Aucun contenu disponible.</div>');
641					}
642				},
643				error: function (xhr, status, error)
644				{
645					console.error('Erreur AJAX:', error);
646					$aside.html('<div class="alert alert-danger m-3">Erreur de chargement : ' + error + '</div>');
647				}
648			});
649		});
650	}
651
652////////////////////////////////////////////////////////////////////////////////
653///////	///////	///////	///////	///////	///////	///////	///////	////////
654////////////////////////////////////////////////////////////////////////////////
655
656	function GetUniqueReviews(IdProduct)
657	{
658		setTimeout(function()
659		{
660			const Id 		= IsEmpty(IdProduct) ? $('#add_to_cart:not(.NoRating) button').attr('data-item-id') : IdProduct,
661				  $TabInfo	= $('#MoreInfoTabs'),
662				  $Parent	= $('#short_description_content'),
663				  $TabDiv	= $('#CenterColumn .MoreInfos');
664			if(!IsEmpty(Id))
665			{
666				$.ajax({
667					type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json",
668					data: {GetUniqueReview : Id},
669					cache: false,
670					success: function(x)
671					{
672						let Average		= Math.ceil(x.ratings.average),
673							Total		= x.ratings.total,
674							Note		= x.ratings.average,
675							TabTitle	= '<li class="mt-2"><a id="MoreInfoTab_Reviews" href="#InfoTabReviews" class="ps-3"><span>'+x.reviews.length+' avis</span></a></li>',
676							HTML		= '<div class="Rating my-1 p-1">';
677						HTML			+= Note > 0 ? '<span class="Note"><b>'+Number(Note)+'</b>/5</span>' : '';
678						HTML			+= Note > 0 ? '<a class="ScrollToReviews" href="#">' : '';
679						HTML			+= HTML_StarNote(Note);
680						HTML			+= Total > 0 ? 'sur '+Total+' avis</a>' : 'Aucun avis';
681						HTML			+= '</div>';
682						if(Total > 0)
683						{
684							HTML			+= '<div class="AllRates">';
685							for(let i = 5; i > 0; i--)
686							{
687								let RateNote 	= x.ratings.distribution[i],
688									DataNote	= RateNote > 0 ? 'data-note="'+Math.ceil(100/Total*RateNote)+'"' : '';
689								HTML		+= '<div class="RateNoteBox"><label>'+i+'</label><div class="RateNote"><div class="ValueRateNote" '+DataNote+' style="width:'+(100/Total*RateNote)+'%"></div><div class="IntNote">'+RateNote+'</div></div></div>';
690							}
691							HTML			+= '</div>';
692						}
693						$Parent.prepend(HTML);
694						if(Total > 0 && $TabInfo.length)
695						{
696							$TabInfo.append(TabTitle);
697							$TabDiv.append('<section id="InfoTabReviews" class="page-product-box p-3 pt-0 BoxRadius HideMoreInfoSection"><h3 class="ScriptFont mt-2 TitleAg">Les avis des internautes</h3><ul></ul></section>');
698							$.each(x.reviews, function(i, Review)
699							{
700								$('#InfoTabReviews ul:not(.ReviewSlides)').append(HTML_Reviews(Review));
701							});
702						}
703					},
704					error: function(){ ErrorAjax('Une erreur est survenue.'); }
705				});
706			}
707		}, 500);
708	}
709
710////////////////////////////////////////////////////////////////////////////////
711///////	///////	///////	///////	///////	///////	///////	///////	////////
712////////////////////////////////////////////////////////////////////////////////
713
714	function ChangeDateFormat()
715	{
716		$(document).on('click', '.snipcart-modal__container:not(.Completed)', function(e)
717		{
718			$(this).addClass('Completed');
719			$('.snipcart-paginated-list').find('.snipcart-order__header-item').each(function()
720			{
721				var $Date = $(this).find('div').last();
722			});
723		});
724	}
725
726////////////////////////////////////////////////////////////////////////////////
727///////	///////	///////	///////	///////	///////	///////	///////	////////
728////////////////////////////////////////////////////////////////////////////////
729
730	function SetHtmlTagToOverflowHidden(Status)
731	{
732		if(Status === true)
733		{
734			$('html').addClass('ModalIsOpen');
735		}
736		else
737		{
738			$('html').removeClass('ModalIsOpen');
739		}
740	}
741
742////////////////////////////////////////////////////////////////////////////////
743///////	///////	///////	///////	///////	///////	///////	///////	////////
744////////////////////////////////////////////////////////////////////////////////
745
746	function ResetHtmlTagOverflow()
747	{
748		$('#ProductModal').on('hidden.bs.modal', function()
749		{
750			SetHtmlTagToOverflowHidden(false);
751		});
752	}
753
754////////////////////////////////////////////////////////////////////////////////
755///////	///////	///////	///////	///////	///////	///////	///////	////////
756////////////////////////////////////////////////////////////////////////////////
757
758	function HTML_AjaxAlert(Message)
759	{
760		return '<div class="alert alert-warning d-flex align-items-center mx-auto mt-4" role="alert">'+
761					'<h4 class="alert-heading">Erreur</h4>'+
762					'<img class="bi flex-shrink-0 me-2" height="24" src="'+RootPath()+'/images/svg/WarningCoral.svg">'+
763					'<div>'+Message+'</div>'+
764				'</div>';
765	}
766
767////////////////////////////////////////////////////////////////////////////////
768///////	///////	///////	///////	///////	///////	///////	///////	////////
769////////////////////////////////////////////////////////////////////////////////
770
771	function isTouchDevice()
772	{
773		return (('ontouchstart' in window) ||
774		(navigator.maxTouchPoints > 0) ||
775		(navigator.msMaxTouchPoints > 0));
776	}
777
778////////////////////////////////////////////////////////////////////////////////
779///////	///////	///////	///////	///////	///////	///////	///////	////////
780////////////////////////////////////////////////////////////////////////////////
781
782	function RootPath()
783	{
784		return window.location.origin+(window.location.hostname === 'securelocalhost' ? '/SacreFrancais.fr' : '');
785	}
786
787////////////////////////////////////////////////////////////////////////////////
788///////	///////	///////	///////	///////	///////	///////	///////	////////
789////////////////////////////////////////////////////////////////////////////////
790
791	function SlideArrows()
792	{
793		return ['<i class="zmdi zmdi-chevron-left"></i>', '<i class="zmdi zmdi-chevron-right"></i>' ];
794	}
795
796////////////////////////////////////////////////////////////////////////////////
797///////	///////	///////	///////	///////	///////	///////	///////	////////
798////////////////////////////////////////////////////////////////////////////////
799
800	function CloneBuyForm()
801	{
802		var $BuyForm			= $('.SingleProductPage #buy_block'),
803			$StickyBuyForm		= $('.SingleProductPage #MainMenu #Sticky_buy_Block'),
804			ProductName			= $BuyForm.find('[data-item-name]').attr('data-item-name');
805		if($BuyForm.length)
806		{
807			if(!$StickyBuyForm.length)
808			{
809				var HTML_Form = $BuyForm.clone();
810				$('#MainMenu').append($(HTML_Form).attr('id', 'Sticky_buy_Block').hide());
811				setTimeout(function()
812				{
813					var Prices		= $('.SingleProductPage .ProductZone #ProductDetailBox')[0].outerHTML;
814					$('.SingleProductPage #MainMenu #Sticky_buy_Block .box-info-product').prepend('<h2 data-old-price="" class="ProductName d-flex">'+ProductName+Prices+'</h2>');
815					$('.SingleProductPage #MainMenu #Sticky_buy_Block .ProductPackContainer').each(function()
816					{
817						var NewId 	= $(this).find('input').attr('id')+'-Sticky';
818						$(this).find('input').attr('id', NewId);
819						$(this).find('label').attr('for', NewId);
820					});
821				}, 10);
822			}
823		}
824	}
825
826////////////////////////////////////////////////////////////////////////////////
827///////	///////	///////	///////	///////	///////	///////	///////	////////
828////////////////////////////////////////////////////////////////////////////////
829
830	function FilteringTastes(That)
831	{
832		var Taste = $(That).val();
833		if(Taste.length > 0)
834		{
835			$('#ProductsList .TagList').hide();
836			$('#ProductsList .TagList a:icontains("'+Taste+'")').parent().show();
837			$('.AlphabeticalContainer').each(function()
838			{
839				if(!$(this).find('.TagList:visible').length)
840				{
841					$(this).hide();
842				}
843				else
844				{
845					$(this).show();
846				}
847			});
848		}
849		else
850		{
851			$('#ProductsList .TagList, .AlphabeticalContainer').fadeIn(100);
852		}
853	}
854
855	const OwlOptions	=	{	loop:				true,
856								margin:				30,
857								nav:				true,
858								autoplay: 			false,
859								autoplayTimeout: 	5000,
860								navText: 			SlideArrows(),
861								dots: 				false,
862								lazyLoad: 			true,
863								responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-2-sp100 lg-2-sp100 xl-3-sp100')
864							};
865
866////////////////////////////////////////////////////////////////////////////////
867///////	///////	///////	///////	///////	///////	///////	///////	////////
868////////////////////////////////////////////////////////////////////////////////
869
870	$('.SF_CookingFamily').owlCarousel(OwlOptions);
871
872////////////////////////////////////////////////////////////////////////////////
873///////	///////	///////	///////	///////	///////	///////	///////	////////
874////////////////////////////////////////////////////////////////////////////////
875
876	$('.SF_MobileProductMainSlide').owlCarousel({
877		loop:				true,
878		margin:				0,
879		nav:				true,
880		autoplay: 			false,
881		autoplayTimeout: 	5000,
882		navText: 			SlideArrows(),
883		dots: 				false,
884		lazyLoad: 			true,
885		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-1-sp0 lg-1-sp0 xl-1-sp0')
886	});
887
888////////////////////////////////////////////////////////////////////////////////
889///////	///////	///////	///////	///////	///////	///////	///////	////////
890////////////////////////////////////////////////////////////////////////////////
891
892	function RecipesSlide(x)
893	{
894		$('.SF-RecipeProductSlide').owlCarousel({
895			loop:				false,
896			margin:				30,
897			nav:				true,
898			autoplay: 			false,
899			autoplayTimeout: 	5000,
900			navText: 			SlideArrows(),
901			dots: 				false,
902			lazyLoad: 			true,
903			responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-2-sp100 lg-2-sp100 xl-3-sp100')
904		});
905	}
906
907////////////////////////////////////////////////////////////////////////////////
908///////	///////	///////	///////	///////	///////	///////	///////	////////
909////////////////////////////////////////////////////////////////////////////////
910
911	function ProductInProductSlide(x)
912	{
913		$('.SF-ProductInProductSlide').owlCarousel({
914			loop:				false,
915			margin:				30,
916			nav:				true,
917			autoplay: 			false,
918			navText: 			SlideArrows(),
919			dots: 				false,
920			lazyLoad: 			true,
921			responsive: 		SET_ItemQuantity('xs-2-sp50 sm-3-sp50 md-3-sp100 lg-4-sp100 xl-4-sp100')
922		});
923		RemoveHandSwipeImg('.SF-ProductInProductSlide');
924	}
925
926////////////////////////////////////////////////////////////////////////////////
927///////	///////	///////	///////	///////	///////	///////	///////	////////
928////////////////////////////////////////////////////////////////////////////////
929
930	function SearchToggler()
931	{
932		var $SearchBtn 	= $('.SearchBtn'),
933			$SearchForm = $('.SearchForm');
934		$SearchBtn.on('click', function(e)
935		{
936			e.preventDefault();
937			$SearchForm.toggleClass('is-visible')
938			setTimeout(function()
939			{
940				if($SearchForm.hasClass('is-visible'))
941				{
942					$('form.SearchInStore input[type="text"]').focus().click();
943				}
944			}, 200);
945		});
946
947		$('.SearchCloseBtn').on('click', function()
948		{
949			$SearchForm.removeClass('is-visible');
950		});
951	}
952
953////////////////////////////////////////////////////////////////////////////////
954///////	///////	///////	///////	///////	///////	///////	///////	////////
955////////////////////////////////////////////////////////////////////////////////
956
957	function ImagesLoaded()
958	{
959		$('#ProductsList, .ProductZoomImg, .item-bestseller, .image-block').imagesLoaded(function()
960		{
961			$('.ProductThumb, .Zoom, .ProductZoomImg').removeClass('LoadingImg');
962		});
963	}
964
965////////////////////////////////////////////////////////////////////////////////
966///////	///////	///////	///////	///////	///////	///////	///////	////////
967////////////////////////////////////////////////////////////////////////////////
968
969	function CheckResponsiveTable()
970	{
971		$('.ResponsiveTable').each(function()
972		{
973			if(this.scrollWidth > this.clientWidth)
974			{
975				$(this).addClass('Slidable');
976			}
977		});
978	}
979
980////////////////////////////////////////////////////////////////////////////////
981///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
982////////////////////////////////////////////////////////////////////////////////
983
984	function IsEmpty(x)
985	{
986		return (x === '' || x === null || typeof x === 'undefined') ? true : false;
987	}
988
989////////////////////////////////////////////////////////////////////////////////
990///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
991////////////////////////////////////////////////////////////////////////////////
992
993	function IsNotEmpty(x)
994	{
995		return (x !== /\s+?/ && x !== '' && x !== null && typeof x !== 'undefined') ? true : false;
996	}
997
998////////////////////////////////////////////////////////////////////////////////
999///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1000////////////////////////////////////////////////////////////////////////////////
1001
1002	function UpdateLanguageFlag()
1003	{
1004		var GoogTrans 	= document.cookie.match('(^|;)\\s*googtrans\\s*=\\s*([^;]+)')?.pop() || '',
1005			LastLang	= GoogTrans.split('/').pop();
1006		$('.TranslationFlags a[data-lang="'+LastLang+'"]').parent().prependTo('.TranslationFlags');
1007	}
1008
1009////////////////////////////////////////////////////////////////////////////////
1010///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1011////////////////////////////////////////////////////////////////////////////////
1012
1013	function FireEvent(That, e)
1014	{
1015		//Source : https://stackoverflow.com/a/9810401
1016		if(document.createEventObject)
1017		{
1018			var evt = document.createEventObject();
1019			return That.fireEvent('on'+e, evt)
1020		}
1021		else
1022		{
1023			var evt = document.createEvent('HTMLEvents');
1024			evt.initEvent(e, true, true );
1025			return !That.dispatchEvent(evt);
1026		}
1027	}
1028
1029////////////////////////////////////////////////////////////////////////////////
1030///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1031////////////////////////////////////////////////////////////////////////////////
1032
1033	function isValidEmailAddress(emailAddress)
1034	{
1035		var pattern = new RegExp(/^(("[\w-+\s]+")|([\w-+]+(?:\.[\w-+]+)*)|("[\w-+\s]+")([\w-+]+(?:\.[\w-+]+)*))(@((?:[\w-+]+\.)*\w[\w-+]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][\d]\.|1[\d]{2}\.|[\d]{1,2}\.))((25[0-5]|2[0-4][\d]|1[\d]{2}|[\d]{1,2})\.){2}(25[0-5]|2[0-4][\d]|1[\d]{2}|[\d]{1,2})\]?$)/i);
1036		return pattern.test(emailAddress);
1037	}
1038
1039////////////////////////////////////////////////////////////////////////////////
1040///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1041////////////////////////////////////////////////////////////////////////////////
1042
1043	function GetProductIds()
1044	{
1045		var Ids = [];
1046		$('#ProductsList li.product').each(function()
1047		{
1048			Ids.push($(this).data('id'));
1049		});
1050		return Ids.join(',');
1051	}
1052
1053////////////////////////////////////////////////////////////////////////////////
1054///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1055////////////////////////////////////////////////////////////////////////////////
1056
1057	function GetLastLoadProductIds()
1058	{
1059		var Ids = [];
1060		$('#ProductsList li.product').each(function()
1061		{
1062			if(!$(this).find('.ProductThumb .StarNote').length)
1063			{
1064				Ids.push($(this).data('id'));
1065			}
1066		});
1067		return Ids.join(',');
1068	}
1069
1070////////////////////////////////////////////////////////////////////////////////
1071///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1072////////////////////////////////////////////////////////////////////////////////
1073
1074	function GetProductCatId()
1075	{
1076		var $That 		= $('#ProductsList li.product').first(),
1077			IdCat 		= IsNotEmpty($That.data('cat')) ? $That.data('cat') : '',
1078			$LoadFilter = $('body[data-load-filter]');
1079		return $LoadFilter.length ? $LoadFilter.attr('data-load-filter') : IdCat;
1080	}
1081
1082////////////////////////////////////////////////////////////////////////////////
1083///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1084////////////////////////////////////////////////////////////////////////////////
1085
1086	function GetProductFilter()
1087	{
1088		var Filter = [];
1089		$('#FilterBlock input[type="checkbox"]:checked').each(function(i,v)
1090		{
1091			Filter.push($(this).val());
1092		});
1093		return Filter.join(',');
1094	}
1095
1096////////////////////////////////////////////////////////////////////////////////
1097///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1098////////////////////////////////////////////////////////////////////////////////
1099
1100	function IsItBlog()
1101	{
1102		return $('#ProductsList .product.Blog').length ? true : null;
1103	}
1104
1105////////////////////////////////////////////////////////////////////////////////
1106///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1107////////////////////////////////////////////////////////////////////////////////
1108
1109	function FilterSelect()
1110	{
1111		var Mode 		= parseInt($('body').attr('data-load-method'),10),
1112			LoadFilter	= $('body').attr('data-load-filter'),
1113			Flavor		= LoadFilter === 'Flavor' ? $('body').attr('data-flavor') : 'Spicy',
1114			Search		= LoadFilter === 'Search' ? $('body').attr('data-search') : '',
1115			$Product	= $('#ProductsList li.product').first(),
1116			SubCat		= $Product.attr('data-subcat'),
1117			Group		= $Product.attr('data-group'),
1118			Filters		= '';
1119		if(Mode === 2)
1120		{
1121			if(LoadFilter === 'Spicy')
1122			{
1123				Filters	= LoadFilter+'|'+Flavor;
1124			}
1125			else if(LoadFilter === 'Search')
1126			{
1127				Filters	= Search;
1128			}
1129			else if(LoadFilter === 'Flavor')
1130			{
1131				Filters	= 'SubCategory|'+Flavor;
1132			}
1133			else
1134			{
1135				Filters	= 'SubCategory|'+SubCat;
1136			}
1137		}
1138		else if(Mode === 3)
1139		{
1140			Filters	= 'Group|'+Group;
1141		}
1142		return Filters;
1143	}
1144
1145////////////////////////////////////////////////////////////////////////////////
1146///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1147////////////////////////////////////////////////////////////////////////////////
1148
1149	function LoadingEnding()
1150	{
1151		setTimeout(function()
1152		{
1153			$('#LoadMoreItems').removeClass('Loading');
1154		}, 1500);
1155	}
1156
1157////////////////////////////////////////////////////////////////////////////////
1158///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1159////////////////////////////////////////////////////////////////////////////////
1160
1161	function FilteringItems($That)
1162	{
1163		$('#ProductsList').fadeTo('slow', 0.3);
1164		setTimeout(function()
1165		{
1166			$That.removeClass('load');
1167			if($('#FilterBlock input[type="checkbox"]:checked').length)
1168			{
1169				let CheckedValues		= [];
1170				$('#ProductsList li.product, #ProductsList .GiftCardContainer, .RandomAdvert').each(function()
1171				{
1172					$(this).addClass('Hidden');
1173				});
1174
1175				$('#FilterBlock input[type="checkbox"]').each(function(i,v)
1176				{
1177					let $That			= $(this);
1178					if($That.is(':checked'))
1179					{
1180						let What 		= $That.data('filter'),
1181							Val 		= $That.val().split('|');
1182						CheckedValues.push({[What]: Val[1]});
1183					}
1184				});
1185				const HasSubcat 		= CheckedValues.some(item 	=> item.subcat),
1186					  $Subcat 			= CheckedValues.filter(item => item.subcat).map(item 	=> item.subcat),
1187					  $Group 			= CheckedValues.filter(item => item.group).map(item 	=> item.group);
1188				$('#ProductsList li.product').each(function()
1189				{
1190					const Subcat 		= $(this).data('subcat'),
1191						  Group 		= $(this).data('group');
1192					if(HasSubcat)
1193					{
1194						if(Subcat && $Subcat.includes(Subcat))
1195						{
1196							$(this).removeClass('Hidden');
1197						}
1198					}
1199					else
1200					{
1201						if(Group && $Group.includes(Group))
1202						{
1203							$(this).removeClass('Hidden');
1204						}
1205					}
1206				});
1207			}
1208			else
1209			{
1210				$('#ProductsList').find('li.product, li.GiftCardContainer, .RandomAdvert').removeClass('Hidden');
1211			}
1212			UpdateGroupFilter();
1213			ImagesLoaded();
1214			$('#ProductsList').fadeTo('slow', 1);
1215			ScrollToAnchor('#ProductsList');
1216		}, 750);
1217
1218
1219		setTimeout(function()
1220		{
1221			FilteringGroupCategory();
1222		}, 850);
1223	}
1224
1225////////////////////////////////////////////////////////////////////////////////
1226///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1227////////////////////////////////////////////////////////////////////////////////
1228
1229	function UpdateGroupFilter()
1230	{
1231		$('#GroupFilter li').addClass('Hidden');
1232		$('#ProductsList li.product:visible').each(function()
1233		{
1234			let Group 		= $(this).data('group').replace(/[^a-zA-Z0-9]/g, '');
1235//			console.log(Group, $('#GroupFilter li[data-group-filter="'+encodeURIComponent(Group)+'"]').length);
1236			$('#GroupFilter li[data-group-filter="'+Group+'"]').removeClass('Hidden');
1237		});
1238	}
1239
1240////////////////////////////////////////////////////////////////////////////////
1241///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1242////////////////////////////////////////////////////////////////////////////////
1243
1244	function FilteringGroupCategory()
1245	{
1246		let $GroupCategory	=	$('.GroupCategory');
1247		if($GroupCategory.length)
1248		{
1249			$GroupCategory.each(function(i,v)
1250			{
1251				if($(this).find('li.product:not([style*="display: none"])').length)
1252				{
1253					$(this).show();
1254				}
1255				else
1256				{
1257					$(this).hide();
1258				}
1259			});
1260		}
1261	}
1262
1263////////////////////////////////////////////////////////////////////////////////
1264///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1265////////////////////////////////////////////////////////////////////////////////
1266
1267	function SendForm(Id)
1268	{
1269		var formulaire = new FormData($('form#'+Id)[0]);
1270		$('form#'+Id+' .alert.ajax').remove();
1271		$.ajax({
1272			type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json",
1273			data: formulaire,
1274			cache: false,
1275			contentType: false,
1276			processData: false,
1277			success: function(x)
1278			{
1279				if(x.hasOwnProperty('Error'))
1280				{
1281					ErrorAjax(x['Error']);
1282				}
1283				else
1284				{
1285					$('form#'+Id).parent().html(x.alerte);
1286				}
1287			},
1288			error: function(){ ErrorAjax('Une erreur est survenue.'); }
1289		});
1290	}
1291
1292////////////////////////////////////////////////////////////////////////////////
1293///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1294////////////////////////////////////////////////////////////////////////////////
1295
1296	function ErrorAjax(Msg)
1297	{
1298/*
1299		$('#ErrorMsg').remove();
1300		$('body').append('<div id="ErrorMsg" class="toast fade show" role="alert" aria-live="assertive" aria-atomic="true" autohide="true">'+
1301							'<div class="toast-header modal-header">'+
1302								'<strong class="mr-auto p-3 pt-0 pb-0">Erreur</strong>'+
1303								'<button type="button" class="ml-2 mb-1 close QuickView-close" data-dismiss="toast" aria-label="Close">
1303'+
1304									'<span aria-hidden="true">&times;</span>'+
1305								'</button>'+
1306							'</div>'+
1307							'<div class="toast-body">'+Msg+'</div>'+
1308						'</div>');
1309*/
1310	}
1311
1312////////////////////////////////////////////////////////////////////////////////
1313///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1314////////////////////////////////////////////////////////////////////////////////
1315
1316	function ErrorLoginAjax(Msg)
1317	{
1318		$('#ProLoginForm').parent().append('<div class="alert alert-warning d-flex align-items-center mb-0 mt-4" role="alert"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" viewBox="0 0 16 16" role="img" aria-label="Warning:"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg><div>'+Msg+'</div></div>');
1319		setTimeout(function()
1320		{
1321			$('#ProLoginForm').parent().find('.alert').fadeOut(250, function()
1322			{
1323				$(this).remove();
1324			});
1325		}, 3000);
1326	}
1327
1328////////////////////////////////////////////////////////////////////////////////
1329///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1330////////////////////////////////////////////////////////////////////////////////
1331
1332	function FormatUrl($Form, Pack)
1333	{
1334		var Url = $Form.find('.snipcart-add-item[data-item-url]').attr('data-item-url');
1335		if(IsNotEmpty(Url))
1336		{
1337			var UrlPart = Url.split('/');
1338//				console.log(Url, UrlPart);
1339			if(UrlPart[UrlPart.length - 2] === 'Carte-cadeau')
1340			{
1341				UrlPart[UrlPart.length - 1]	= Pack+'.html';
1342//				console.log(UrlPart);
1343			}
1344			else
1345			{
1346				UrlPart[UrlPart.length - 2]	= Pack;
1347			}
1348			return UrlPart.join('/');
1349		}
1350	}
1351
1352////////////////////////////////////////////////////////////////////////////////
1353///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1354////////////////////////////////////////////////////////////////////////////////
1355
1356	function ScrollToAnchor(Id)
1357	{
1358		var MenuHeight		= $('#MainMenu').height(),
1359			BuyFormHeight	= $('.SingleProductPage #buy_block').length ? 80 : 0;
1360		if(IsEmpty(Id) && location.hash)
1361		{
1362			var Id 			= location.hash.replace('/','');
1363		}
1364		if(IsNotEmpty(Id) && $(Id).length)
1365		{
1366			$(Id).prop('open', true);
1367			$('html,body').animate({scrollTop: $(Id).offset().top-MenuHeight-(20+BuyFormHeight)},50);
1368		}
1369	}
1370
1371////////////////////////////////////////////////////////////////////////////////
1372///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1373////////////////////////////////////////////////////////////////////////////////
1374
1375	function SET_Taxes(Taxe)
1376	{
1377		let TvaName = '';
1378		switch(Taxe)
1379		{
1380			case '5.5' :	TvaName = 'TVA ('+Taxe+'%)'; break;
1381			default :		TvaName = 'TVA ('+Taxe+'%)'; break;
1382		}
1383		return TvaName;
1384	}
1385
1386////////////////////////////////////////////////////////////////////////////////
1387///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1388////////////////////////////////////////////////////////////////////////////////
1389
1390	function SET_ItemQuantity(Level)
1391	{
1392		// Exemple : 'xs-1-sp10 sm-2-sp100 md-2-sp50 lg-4-sp100'
1393		// xs = BreakPoint - 1 = Number of items - sp10 = Stage padding 10 px
1394		const 	BreakPoints 						= { xs: 0, sm: 576, md: 768, lg: 992, xl: 1920 };
1395		let 	ResponsiveSettings					= {};
1396
1397		Level.split(' ').forEach(Setting => {
1398			let [BreakPointItems, StagePadding] 	= Setting.split('-sp'),
1399				[BreakPoint, ItemsSlideBy] 			= BreakPointItems.split('-');
1400
1401			ResponsiveSettings[BreakPoints[BreakPoint]] = 
1402			{
1403				items: 			parseInt(ItemsSlideBy,10),
1404				slideBy: 		parseInt(ItemsSlideBy,10),
1405				stagePadding: 	parseInt(StagePadding,10)
1406			};
1407		});
1408		return ResponsiveSettings;
1409	}
1410
1411////////////////////////////////////////////////////////////////////////////////
1412///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1413////////////////////////////////////////////////////////////////////////////////
1414
1415	function SET_DeliveryToast(Message)
1416	{
1417		const $ToastBox 	= document.getElementById('ToastBox');
1418		if($ToastBox.children.length > 0)
1419		{
1420			[...$ToastBox.children].forEach(toast => toast.remove());
1421		}
1422		const ToastHTML = `<div class="toast align-items-center text-bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true">
1423								<div class="d-flex flex-column">
1424									<div class="toast-header">
1425										<img height="15px" src="${RootPath()}/images/svg/BoxYellow.svg" class="rounded me-2" alt="Info livraison">
1426										<strong class="me-auto">Infos livraison</strong>
1427										<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close">&times;</button>
1428									</div>
1429									<div class="toast-body ShippingInfo FreeShipping">
1430										${Message}
1431									</div>
1432								</div>
1433							</div>`;
1434		$ToastBox.insertAdjacentHTML('beforeend', ToastHTML);
1435		const $ToastItem 	= $ToastBox.lastElementChild; 
1436		const $Toast 		= new bootstrap.Toast($ToastItem, { delay: 5000 }); 
1437		$Toast.show();
1438		$ToastItem.addEventListener('hidden.bs.toast', function()
1439		{
1440			$ToastItem.remove();
1441		});
1442	}
1443
1444////////////////////////////////////////////////////////////////////////////////
1445///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1446////////////////////////////////////////////////////////////////////////////////
1447
1448	function UpdateCartFunctions()
1449	{
1450		$('.Zoom').zoom({touch: false});
1451		var $Thumbs 		= $('.ThumbNailZoom'),
1452			SyncedSecondary	= true;
1453		$Thumbs.owlCarousel(
1454		{
1455			margin: 				20,
1456			dots: 					false,
1457			nav: 					true,
1458			navText: 				SlideArrows(),
1459			loop:					false,
1460			smartSpeed: 			200,
1461			slideSpeed: 			500,
1462			slideBy: 				1,
1463			responsiveRefreshRate:	100,
1464			responsive: 			SET_ItemQuantity('xs-3-sp50 sm-3-sp50 md-3-sp0 lg-4-sp0 xl-4-sp0')
1465		});
1466		RemoveHandSwipeImg('.ThumbNailZoom');
1467		RecipesSlide(3);
1468		ProductInProductSlide(4);
1469		$Thumbs.on('click', 'img', function(e)
1470		{
1471			var $That 		= $(this),
1472				$BigImage	= $That.closest('.ProductZoomImg');
1473			$BigImage.find('.Zoom').addClass('LoadingImg');
1474			$BigImage.find('.Zoom').trigger('zoom.destroy');
1475			$BigImage.find('.Zoom img').attr('src',$That.attr('src').replace('mini_','700_'));
1476			$BigImage.find('.Zoom img').attr('data-src',$That.attr('src').replace('mini_','700_'));
1477			$BigImage.find('.Zoom figcaption').text($That.attr('alt'));
1478			setTimeout(function()
1479			{
1480				$('.Zoom').zoom({touch: false});
1481				ImagesLoaded();
1482			}, 50);
1483		});
1484
1485		$(document).on('change', 'input[name="PixProductPackaging[]"], input[name^="PixProductPackaging["][name$="]"]', function(e)
1486		{
1487//			console.log(this);
1488			if($(this).hasClass('Page'))
1489			{
1490				let IsChecked			= $(this).prop('checked');
1491				$('input.PixProductPackaging:not(.Modal)[value="'+this.value+'"]').each(function()
1492				{
1493					$(this).prop('checked', IsChecked);
1494				});
1495			}
1496
1497			var $That				= $(this),
1498				Img					= $That.val(),
1499				$Form				= $That.closest('form'),
1500				FormId				= $Form.attr('data-form-id'),
1501				$Forms				= $('[data-form-id="'+FormId+'"]'),
1502				Price 				= $That.attr('data-price'),
1503				Name 				= $That.attr('data-name'),
1504				Gtin 				= $That.attr('data-gtin'),
1505				OldPrice 			= $That.attr('data-old-price'),
1506				BioLabel 			= $That.attr('data-bio-label'),
1507				PackagingWeight		= $That.attr('data-packaging-weight'),
1508				Weight				= IsNotEmpty($That.attr('data-weight')) ? $That.attr('data-weight') : 0,
1509				TotalWeight			= IsNotEmpty($That.attr('data-weight')) ? parseInt(PackagingWeight,10) : 0,
1510				Capacity			= $That.attr('data-capacity'),
1511				Taxe				= SET_Taxes($That.attr('data-taxe')),
1512				Promo				= $That.attr('data-promo'),
1513				Unlabelled			= ['Eco'],
1514				Label				= $That.attr('data-label') !== '1' || Unlabelled.includes(Img)? 'Conditionnement sans étiquette' : 'Conditionnement avec étiquette',
1515				Url					= FormatUrl($Form, Img),
1516				$PriceZone			= $That.closest('.ProductZone').find('.ProductPriceZone'),
1517				$PriceZoneMenu		= $That.closest('#Sticky_buy_Block').find('.ProductPriceZone'),
1518				EuroSymbol			= '<sup itemprop="priceCurrency" content="EUR" class="Euro">€</sup>',
1519				PriceLabel 			= Price.split('.'),
1520				HTMLPrice			= IsNotEmpty(PriceLabel[1]) ? PriceLabel[0]+'<span class="Decimal">.'+PriceLabel[1] : PriceLabel[0],
1521				PriceLabel			= '<span class="" itemprop="price" content="'+Price+'">'+HTMLPrice+EuroSymbol+'</span>',
1522				$ProductLongDetail 	= $That.closest('.ProductLongDetail');
1523
1524			$PriceZone.attr('content',Price).find('.Price').html(PriceLabel);
1525			$PriceZoneMenu.attr('content',Price).find('.Price').html(PriceLabel);
1526			$('#image-block .price').html(PriceLabel);
1527			$ProductLongDetail.find('.Gramme').text(Weight+'g');
1528			$ProductLongDetail.find('.Capacity').text(Capacity+'oz');
1529			$ProductLongDetail.find('.GtinNumber').text(Gtin);
1530			$('#CenterColumn.ProductLongDetail').attr('data-bio-label', BioLabel);
1531			$('.FakeBox.LabelTemplate.Box').attr('data-packaging', Img.replace('Eco','Zip'));
1532
1533			$Forms.each(function(i)
1534			{
1535				let $Form = $(this);
1536				$Form.find('.snipcart-add-item[data-item-custom1-value]').attr('data-item-custom1-value', Img);
1537				$Form.find('.snipcart-add-item[data-item-custom2-value]').attr('data-item-custom2-value', Weight);
1538				$Form.find('.snipcart-add-item[data-item-custom3-value]').attr('data-item-custom3-value', Capacity);
1539				$Form.find('.snipcart-add-item[data-item-custom4-value]').attr('data-item-custom4-value', Label);
1540				$Form.find('.snipcart-add-item[data-item-price]').attr('data-item-price', Price.replace(',', '.'));
1541				$Form.find('.snipcart-add-item[data-item-taxes]').attr('data-item-taxes', Taxe);
1542				if(parseInt(PackagingWeight,10)=== 0)
1543				{
1544					$Form.find('.snipcart-add-item[data-item-name]').attr('data-item-name', Name);
1545				}
1546				$Form.find('.snipcart-add-item[data-item-weight]').attr('data-item-weight', TotalWeight);
1547				$Form.find('.snipcart-add-item[data-item-url]').attr('data-item-url', Url);
1548			});
1549			if(IsNotEmpty(Promo) && Promo !== '0')
1550			{
1551				$ProductLongDetail.find('.reduction_percent_display').text('-'+Promo+'%').show();
1552//				$ProductLongDetail.find('.ProductDetailBox .old-price .OldPrice').html(OldPrice+'<sup class="Euro">€</sup>').closest('.old-price').show();
1553//				console.log($ProductLongDetail.find('[data-old-price].price').length);
1554				$ProductLongDetail.find('.ProductDetailBox [data-old-price]').attr('data-old-price', OldPrice+'€');
1555				$ProductLongDetail.find('[data-old-price].price').attr('data-old-price', OldPrice+'€');
1556				$('#Sticky_buy_Block [data-old-price]').attr('data-old-price', OldPrice+'€');
1557			}
1558			else
1559			{
1560				$ProductLongDetail.find('.reduction_percent_display').hide();
1561				$That.closest('.ProductLongDetail').find('.ProductDetailBox .old-price').hide();
1562				$ProductLongDetail.find('.ProductDetailBox [data-old-price]').attr('data-old-price', '');
1563				$ProductLongDetail.find('[data-old-price].price').attr('data-old-price', '');
1564				$('#Sticky_buy_Block [data-old-price]').attr('data-old-price', '');
1565			}
1566		});
1567
1568
1569		$(document).on('click', '#snipcart, .snipcart__box--header button[type="button"]', function(e)
1570		{
1571			StickyPaymentStepsButtons('Clic sur modifier');
1572		});
1573		setTimeout(function()
1574		{
1575			$('.product_page').addClass('Hide50');
1576		}, 100);
1577		setTimeout(function()
1578		{
1579			$('.product_page').removeClass('Hide Hide50');
1580		}, 1000);
1581	}
1582
1583////////////////////////////////////////////////////////////////////////////////
1584///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1585////////////////////////////////////////////////////////////////////////////////
1586
1587	function IsLocalStorageAvailable()
1588	{
1589		var test = 'test';
1590		try
1591		{
1592			localStorage.setItem(test, test);
1593			localStorage.removeItem(test);
1594			return true;
1595		}
1596		catch(e)
1597		{
1598			return false;
1599		}
1600	}
1601
1602////////////////////////////////////////////////////////////////////////////////
1603///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1604////////////////////////////////////////////////////////////////////////////////
1605
1606	function TrimOldEntries(Data, limit = 30)
1607	{
1608		const Entries 			= Object.entries(Data).sort(([, a], [, b]) => new Date(a.date) - new Date(b.date)),
1609			  TrimmedEntries 	= Entries.slice(-limit);
1610		return Object.fromEntries(TrimmedEntries);
1611	}
1612
1613////////////////////////////////////////////////////////////////////////////////
1614///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1615////////////////////////////////////////////////////////////////////////////////
1616
1617	function GET_ProductsPackageTemplates(Datas)
1618	{
1619//		console.log(Datas);
1620		$.ajax({
1621			type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json",
1622			data: { GetPackageTemplate: true, PackageDatas:TrimOldEntries(Datas)},
1623			success: function(x)
1624			{
1625				$('#ViewedProducts').find('.SF-ViewedProductSlide').append(x.HTML);
1626				setTimeout(function()
1627				{
1628					let $ViewedProductSlide	= $('.SF-ViewedProductSlide');
1629					$ViewedProductSlide.owlCarousel({
1630						loop:				false,
1631						margin:				0,
1632						nav:				true,
1633						autoplay: 			false,
1634						navText: 			SlideArrows(),
1635						dots: 				false,
1636						lazyLoad: 			true,
1637						responsive:			SET_ItemQuantity('xs-3-sp0 sm-3-sp0 md-6-sp0 lg-6-sp0 xl-8-sp0')
1638					});
1639				}, 100);
1640			}
1641		});
1642	}
1643
1644////////////////////////////////////////////////////////////////////////////////
1645///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1646////////////////////////////////////////////////////////////////////////////////
1647
1648	function StoreLogsOnError(Issue, SnipCartState)
1649	{
1650		$.ajax({
1651			type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json",
1652			data: { ErrorLogs: true, Errors:JSON.stringify(Issue), State:JSON.stringify(SnipCartState)},
1653			success: function(x)
1654			{
1655				console.log(Issue);
1656			},
1657			error: function(){ console.log(Issue); }
1658		});
1659	}
1660
1661////////////////////////////////////////////////////////////////////////////////
1662///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1663////////////////////////////////////////////////////////////////////////////////
1664
1665	function StoreViewedProduct()
1666	{
1667		if(IsLocalStorageAvailable())
1668		{
1669			let $ProductBtn 	= $('.snipcart-add-item:not(.NoViewedProduct)'),
1670				ViewedProduct	= localStorage.getItem('ViewedProduct') === null ? {} : JSON.parse(localStorage.getItem('ViewedProduct'));
1671			if($ProductBtn.length)
1672			{
1673				$ProductBtn.each(function(i)
1674				{
1675					let $That			= $(this),
1676						ProductUrl		= $That.attr('data-item-url'),
1677						Productid		= $That.attr('data-item-id'),
1678						ProductCatId	= $That.parent().attr('data-id-categorie'),
1679						ProductSubCat	= $That.parent().attr('data-sub-cat'),
1680						ProductGroup	= $That.parent().attr('data-group'),
1681						ProductPacks	= $That.parent().attr('data-packages'),
1682						ProductPack		= $That.attr('data-item-custom1-value');
1683
1684					ViewedProduct[Productid] 				= 	{};
1685					ViewedProduct[Productid]['IdCat']		=	ProductCatId;
1686					ViewedProduct[Productid]['SubCat']		=	ProductSubCat;
1687					ViewedProduct[Productid]['Group']		=	ProductGroup;
1688					ViewedProduct[Productid]['Package']		=	ProductPack;
1689					ViewedProduct[Productid]['Packages']	=	ProductPacks;
1690//					ViewedProduct[Productid]['img']			=	$That.attr('data-item-image');
1691					ViewedProduct[Productid]['url']			=	ProductUrl;
1692					ViewedProduct[Productid]['name']		=	$That.attr('data-item-name');
1693					ViewedProduct[Productid]['date']		=	new Date();
1694					ViewedProduct[Productid]['status']		=	'Viewed';
1695					localStorage.setItem('ViewedProduct', JSON.stringify(ViewedProduct));
1696				});
1697			}
1698		}
1699	}
1700
1701////////////////////////////////////////////////////////////////////////////////
1702///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1703////////////////////////////////////////////////////////////////////////////////
1704
1705	function UpdateViewedProduct(ProductId, Status)
1706	{
1707		let $ViewedProducts			= $('#ViewedProducts'),
1708			IsViewedProductActive 	= $ViewedProducts.parent().find('.snipcart-add-item.NoViewedProduct').length ? false : true,
1709			ViewedProducts			= JSON.parse(localStorage.getItem('ViewedProduct'));
1710		if(IsViewedProductActive && $ViewedProducts.length && localStorage.getItem('ViewedProduct') !== null)
1711		{
1712			if(IsNotEmpty(ViewedProducts[ProductId]) && IsNotEmpty(ViewedProducts[ProductId]['status']))
1713			{
1714				ViewedProducts[ProductId]['status']	=	Status === 'Add' ? 'AddToCart' : 'Viewed';
1715				localStorage.setItem('ViewedProduct', JSON.stringify(ViewedProducts));
1716				DisplayViewedProducts();
1717			}
1718		}
1719	}
1720
1721////////////////////////////////////////////////////////////////////////////////
1722///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1723////////////////////////////////////////////////////////////////////////////////
1724
1725	function DisplayViewedProducts()
1726	{
1727		let $ViewedProducts	= $('#ViewedProducts'),
1728			ViewedProducts	= JSON.parse(localStorage.getItem('ViewedProduct')),
1729			TotalProducts	= IsNotEmpty(ViewedProducts) ? Object.keys(ViewedProducts).length : 0;
1730		if($ViewedProducts.length && localStorage.getItem('ViewedProduct') !== null)
1731		{
1732			$ViewedProducts.find('.SF-ViewedProductSlide').empty();
1733			$('.SF-ViewedProductSlide').owlCarousel('destroy');
1734			GET_ProductsPackageTemplates(ViewedProducts);
1735			$ViewedProducts.find('.owl-stage:empty').remove();
1736			$ViewedProducts.removeClass('d-none');
1737		}
1738		else
1739		{
1740			$ViewedProducts.addClass('d-none');
1741		}
1742	}
1743
1744////////////////////////////////////////////////////////////////////////////////
1745///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1746////////////////////////////////////////////////////////////////////////////////
1747
1748	function EstimateShipping(Snipcart)
1749	{
1750		const state 		= Snipcart.store.getState();
1751		var  totalWeight 	= state.cart.items.items.reduce((acc, item) => { 
1752			return acc + item.quantity * item.dimensions.weight;
1753		}, 0);
1754		if(totalWeight > 0)
1755		{
1756			$.get(RootPath()+'/Shipping/ShippingSimulator.php?Simulate='+totalWeight, function(ShippingTax)
1757			{
1758				$('.ShippingInfo small, .ShippingEvaluation small').append(' Économisez <strong>'+parseFloat(ShippingTax).toFixed(2).replace(/\.00/,'')+'<sup>€</sup></strong> de frais de livraison en France métropolitaine, Andorre et Monaco (estimation). <a href="'+RootPath()+'/cgv.php#CGV_Livraison" target="_blank"><small><u>Consultez les tarifs de livraison</u></small></a>');
1759			}, 'text');
1760		}
1761	}
1762
1763///////////////////////////////////////////////////////////////////////////////
1764///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1765////////////////////////////////////////////////////////////////////////////////
1766
1767	function SetShippingDates()
1768	{
1769		let today = new Date();
1770		function addDays(date, days)
1771		{
1772			let result = new Date(date);
1773			result.setDate(result.getDate() + days);
1774			if(result.getDay() === 0)
1775			{ 
1776				result.setDate(result.getDate() + 1);
1777			}
1778			return result;
1779		}
1780		let handlingTime = 24;
1781		if(today.getDay() === 0)
1782		{
1783			handlingTime = 48;
1784		}
1785		else if(today.getDay() === 6)
1786		{
1787			handlingTime = 72;
1788		}
1789		let minDeliveryDate = new Date(today);
1790		minDeliveryDate.setHours(minDeliveryDate.getHours() + handlingTime);
1791
1792		if(minDeliveryDate.getDay() === 0) {
1793			minDeliveryDate.setDate(minDeliveryDate.getDate() + 1);
1794		}
1795
1796		let maxDeliveryDate = addDays(minDeliveryDate, 5);
1797
1798		function formatDate(date) 
1799		{
1800			const days 		= ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'];
1801			const months 	= ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'];
1802			let dayName 	= days[date.getDay()];
1803			let day 		= date.getDate();
1804			let month 		= months[date.getMonth()];
1805
1806			return { dayName, day, month };
1807		}
1808
1809		let minFormattedDate = formatDate(minDeliveryDate);
1810		let maxFormattedDate = formatDate(maxDeliveryDate);
1811
1812		if(minFormattedDate.month === maxFormattedDate.month) {
1813			return `le ${minFormattedDate.dayName} ${minFormattedDate.day} et le ${maxFormattedDate.dayName} ${maxFormattedDate.day}
1813 ${minFormattedDate.month}`;
1814		} else {
1815			return `le ${minFormattedDate.dayName} ${minFormattedDate.day} ${minFormattedDate.month} et le ${maxFormattedDate.dayName} ${maxFormattedDate.day} ${maxFormattedDate.month}`;
1816		}
1817	}
1818
1819////////////////////////////////////////////////////////////////////////////////
1820///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1821////////////////////////////////////////////////////////////////////////////////
1822
1823	function SetFreeShipping(Snipcart)
1824	{
1825		setTimeout(function()
1826		{
1827			const TotalPrice = Snipcart.store.getState().cart.total;
1828			if(!$('.ShippingInfo').length)
1829			{
1830				$('.snipcart-cart__footer').before('<div class="ShippingInfo"></div>')
1831			}
1832			let	IsDigitalGoods		= ($('form[data-form-id="GiftCard"]').length || $('form[data-form-id="Atelier"]').length) ? true : false,
1833				FreeShipLimit		= 65,
1834				RestFreeShipping	= TotalPrice < FreeShipLimit ? FreeShipLimit - TotalPrice : 0,
1835				$ShippingInfo		= IsDigitalGoods ? $('.ShippingInfo') : $('.ShippingInfo, .ShippingEvaluation'),
1836				$ShippingNotice		= $('.snipcart-summary-fees__notice'),
1837				$ShippingInfoCart	= $('.snipcart-loading-overlay__content .snipcart-summary-fees'),
1838				ShippingMessage		= '<p class="fw-normal">Livré en point de retrait Mondial Relay entre <strong>'+SetShippingDates()+'</strong><br><small>(pour toute livraison en France Métropolitaine)</small></p><p class="fw-normal mt-2">Les colis sont <strong>personnalisés manuellement</strong> par notre équipe qui s’efforcera de les expédier en <strong>moins de 48h</strong>.</p>',
1839				ShippingStatus		= '';
1840			if(RestFreeShipping > 0)
1841			{
1842				if(RestFreeShipping < 30)
1843				{
1844					ShippingStatus = 'Plus que <span>'+RestFreeShipping.toFixed(2).replace(/\.00/,'')+'<sup>€</sup></span> avant la livraison offerte.'+ShippingMessage;
1845					$ShippingInfo.addClass('FreeShipping').html(ShippingStatus);
1846				}
1847				else
1848				{
1849					ShippingStatus = 'La livraison offerte dès '+FreeShipLimit+'<sup>€</sup> d’achat.'+ShippingMessage;
1850					$ShippingInfo.addClass('FreeShipping').html(ShippingStatus);
1851				}
1852				$ShippingNotice.show();
1853				EstimateShipping(Snipcart);
1854			}
1855			else
1856			{
1857				ShippingStatus = '<b class="ScriptFont">Félicitation !</b> La livraison vous est offerte. '+ShippingMessage;
1858				$ShippingInfo.addClass('FreeShipping').html(ShippingStatus);
1859				$ShippingNotice.hide();
1860			}
1861			SET_DeliveryToast(ShippingStatus);
1862
1863			const state = Snipcart.store.getState();
1864			var totalWeight = state.cart.items.items.reduce((acc, item) => { 
1865					return acc + item.quantity * item.dimensions.weight;
1866			}, 0);
1867		}, 100);
1868	}
1869
1870////////////////////////////////////////////////////////////////////////////////
1871///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1872////////////////////////////////////////////////////////////////////////////////
1873
1874	function SET_OrderErrorMessage(Msg)
1875	{
1876		let Li 		= '';
1877		if(Msg === 'domain_crawling')
1878		{
1879			Li = '<li class="Check"><b><u>Vous n’avez pas été débité.</u></b></li>';
1880		}
1881		return '<span class="text-black fw-bold text-uppercase mt-2 fs-6">Pas de panique ! &#x1F60C;</span><ul>'+Li+'<li class="Check">Notre service web hyper réactif (si, si !) vient d’être averti de cette erreur et vous contactera si nécessaire.</li><li class="Check">Vous pouvez contacter l’équipe de Sacré français au <a class="LinkTel d-inline-flex" href="tel:+33652202613"><img class="me-1" height="15" src="'+RootPath()+'/images/svg/PhoneWhite.svg" alt="Téléphone">06&nbsp;52&nbsp;20&nbsp;26&nbsp;13</a> pour plus de détails ou pour passer commande si c’est une urgence (la gourmandise n’attend pas !).</li></ul>';
1882	}
1883
1884////////////////////////////////////////////////////////////////////////////////
1885///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1886////////////////////////////////////////////////////////////////////////////////
1887
1888	function SetAnimatedSlide(e)
1889	{
1890		$(e.target).find('.owl-item .slide').removeClass('opacity-0');
1891		var $ActiveSlide 	= $(e.target).find('.owl-item.active .SlideHomeAnimate'),
1892			$InactiveSlide	= $(e.target).find('.owl-item:not(.active) .SlideHomeAnimate');
1893		$ActiveSlide.removeClass('d-none').addClass('d-flex animated fadeInRight');
1894		setTimeout(function()
1895		{
1896			$ActiveSlide.find('div').first().addClass('animated fadeInLeft');
1897			$InactiveSlide.removeClass('d-flex animated fadeInRight').addClass('d-none');
1898			$InactiveSlide.find('.animated').removeClass('animated fadeInLeft');
1899		}, 100);
1900		setTimeout(function()
1901		{
1902			$(e.target).find('.owl-item .slide').css({'height':$(e.target).find('.owl-item.active').outerHeight()+'px'});
1903		}, 110);
1904	}
1905
1906////////////////////////////////////////////////////////////////////////////////
1907///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1908////////////////////////////////////////////////////////////////////////////////
1909
1910	$('#CopyLink').on('click', function(e)
1911	{
1912		e.preventDefault();
1913		const linkToCopy = $(this).data('link');
1914		navigator.clipboard.writeText(linkToCopy).then(function()
1915		{
1916			$('.SvgShareBtn').toggleClass('d-none').parent().find('.CopyLinkText').text('Copié !');
1917			setTimeout(function()
1918			{
1919				$('.SvgShareBtn').toggleClass('d-none').parent().find('.CopyLinkText').text('Copier le lien');
1920			}, 2000);
1921		}).catch(function(err)
1922		{
1923			console.error('Erreur lors de la copie :', err);
1924		});
1925	});
1926
1927////////////////////////////////////////////////////////////////////////////////
1928///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1929////////////////////////////////////////////////////////////////////////////////
1930
1931	if($('nav.mobilemenu__nav').length)
1932	{
1933		$('nav.mobilemenu__nav').meanmenu({
1934			meanMenuClose: '&times;',
1935			meanMenuCloseSize: '18px',
1936			meanScreenWidth: '575',
1937			meanExpandableChildren: true,
1938			meanMenuContainer: '.mobile-menu',
1939			onePage: true
1940		});
1941	}
1942
1943////////////////////////////////////////////////////////////////////////////////
1944///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1945////////////////////////////////////////////////////////////////////////////////
1946
1947	$('.SlideInBlog').owlCarousel({
1948		loop:true,
1949		margin:50,
1950		nav:true,
1951		autoplay: true,
1952		autoplayTimeout: 10000,
1953		items:1,
1954		navText: SlideArrows(),
1955		dots: false,
1956		lazyLoad: true,
1957	});
1958	RemoveHandSwipeImg('.SlideInBlog');
1959
1960////////////////////////////////////////////////////////////////////////////////
1961///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
1962////////////////////////////////////////////////////////////////////////////////
1963
1964	$('#ReviewSlides').owlCarousel({
1965		loop:				true,
1966		margin:				50,
1967		nav:				true,
1968		autoplay: 			true,
1969		autoplayTimeout:	10000,
1970		autoHeight: 		true,
1971		items:				3,
1972		navText: 			SlideArrows(),
1973		dots: 				false,
1974		lazyLoad: 			true,
1975		onInitialized: function(e)
1976		{
1977			$('#ReviewSlides').removeClass('opacity-0');
1978		},
1979		responsive: {
1980			0:{
1981				items:1,
1982				slideBy: 1
1983			},
1984			576:{
1985				items:2,
1986				slideBy: 2
1987			},
1988			768:{
1989				items:2,
1990				slideBy: 2
1991			},
1992			992:{
1993				items:3,
1994				slideBy: 3
1995			},
1996			1920:{
1997				items:4,
1998				slideBy: 4
1999			}
2000		}
2001	});
2002	RemoveHandSwipeImg('#ReviewSlides');
2003
2004////////////////////////////////////////////////////////////////////////////////
2005///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2006////////////////////////////////////////////////////////////////////////////////
2007
2008	$('#BlogHomeList').owlCarousel({
2009		loop:				true,
2010		margin:				30,
2011		nav:				true,
2012		autoplay: 			false,
2013		navText: 			SlideArrows(),
2014		dots: 				false,
2015		lazyLoad: 			true,
2016		onInitialized: function(e)
2017		{
2018			$('.SF_BlogArea').removeClass('opacity-0');
2019		},
2020		responsive: SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-2-sp100 lg-3-sp100 xl-4-sp100')
2021	});
2022
2023////////////////////////////////////////////////////////////////////////////////
2024///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2025////////////////////////////////////////////////////////////////////////////////
2026
2027	$('.SlideInProduct').owlCarousel({
2028		loop:				true,
2029		lazyLoad: 			true,
2030		margin:				15,
2031		navigation:			true,
2032		slideSpeed:			1000,
2033		nav:				true,
2034		pagination:			false,
2035		navText: 			SlideArrows(),
2036		dots: 				false,
2037		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-3-sp50 md-3-sp0 lg-4-sp0 xl-4-sp0')
2038	});
2039
2040////////////////////////////////////////////////////////////////////////////////
2041///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2042////////////////////////////////////////////////////////////////////////////////
2043
2044	$('.pos-topsellers').owlCarousel({
2045		loop:				true,
2046		lazyLoad: 			true,
2047		items:				1,
2048		navigation:			true,
2049		slideSpeed:			1000,
2050		nav:				true,
2051		pagination:			false,
2052		navText: 			SlideArrows(),
2053		dots: 				false
2054	});
2055	RemoveHandSwipeImg('.pos-topsellers');
2056
2057////////////////////////////////////////////////////////////////////////////////
2058///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2059////////////////////////////////////////////////////////////////////////////////
2060
2061	$('.BookPages').owlCarousel({
2062		loop:				true,
2063		lazyLoad: 			true,
2064		items:				1,
2065		autoplay: 			true,
2066		autoplayTimeout: 	10000,
2067		navigation:			true,
2068		slideSpeed:			1000,
2069		nav:				true,
2070		pagination:			false,
2071		navText: 			SlideArrows(),
2072		dots: 				false
2073	});
2074	RemoveHandSwipeImg('.BookPages');
2075
2076////////////////////////////////////////////////////////////////////////////////
2077///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2078////////////////////////////////////////////////////////////////////////////////
2079
2080	$('.SF_SliderFeature').owlCarousel({
2081		loop:				true,
2082		margin:				0,
2083		nav:				false,
2084		autoplay: 			true,
2085		autoplayTimeout: 	10000,
2086		items:				6,
2087		navText: 			SlideArrows(),
2088		dots: 				false,
2089		lazyLoad: 			true,
2090		responsive: 		SET_ItemQuantity('xs-3-sp50 sm-3-sp50 md-5-sp0 lg-5-sp0 xl-5-sp0')
2091	});
2092	RemoveHandSwipeImg('.SF_SliderFeature');
2093
2094////////////////////////////////////////////////////////////////////////////////
2095///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2096////////////////////////////////////////////////////////////////////////////////
2097
2098	$('#ProductBoxesByLine').owlCarousel({
2099		loop:				true,
2100		margin:				0,
2101		nav:				true,
2102		autoplay: 			false,
2103		autoplayTimeout: 	10000,
2104		navText: 			SlideArrows(),
2105		dots: 				false,
2106		lazyLoad: 			true,
2107		responsive: 		SET_ItemQuantity('xs-3-sp0 sm-3-sp0 md-6-sp0 lg-6-sp0 xl-8-sp0')
2108	});
2109
2110////////////////////////////////////////////////////////////////////////////////
2111///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2112////////////////////////////////////////////////////////////////////////////////
2113
2114	$('.BestProductSlider').owlCarousel({
2115		loop:				false,
2116		margin:				30,
2117		nav:				true,
2118		autoplay: 			false,
2119		autoplayTimeout: 	10000,
2120		items:				3,
2121		navText: 			SlideArrows(),
2122		dots: 				false,
2123		lazyLoad: 			true,
2124		responsive: 		SET_ItemQuantity('xs-2-sp50 sm-3-sp50 md-3-sp100 lg-4-sp100 xl-4-sp100')
2125	});
2126//	RemoveHandSwipeImg('.BestProductSlider');
2127
2128////////////////////////////////////////////////////////////////////////////////
2129///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2130////////////////////////////////////////////////////////////////////////////////
2131
2132	$('.SF-NewProductSlide').owlCarousel({
2133		loop:				true,
2134		margin:				30,
2135		nav:				true,
2136		autoplay: 			false,
2137		autoplayTimeout: 	10000,
2138		items:				4,
2139		navText: 			SlideArrows(),
2140		dots: 				false,
2141		lazyLoad: 			true,
2142		onInitialized: function(e)
2143		{
2144			$('#NewProducts').removeClass('opacity-0');
2145		},
2146		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-3-sp50 md-3-sp0 lg-4-sp0 xl-4-sp0')
2147	});
2148//	RemoveHandSwipeImg('.SF-NewProductSlide');
2149
2150////////////////////////////////////////////////////////////////////////////////
2151///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2152////////////////////////////////////////////////////////////////////////////////
2153
2154	RecipesSlide(2);
2155	ProductInProductSlide(3);
2156
2157////////////////////////////////////////////////////////////////////////////////
2158///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2159////////////////////////////////////////////////////////////////////////////////
2160
2161	$('.SF-MarketSlide').owlCarousel({
2162		loop:				true,
2163		margin:				30,
2164		nav:				true,
2165		autoplay: 			false,
2166		autoplayTimeout: 	10000,
2167		navText: 			SlideArrows(),
2168		dots: 				false,
2169		lazyLoad: 			true,
2170		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-2-sp100 lg-3-sp100 xl-4-sp100')
2171	});
2172	RemoveHandSwipeImg('.SF-MarketSlide');
2173
2174////////////////////////////////////////////////////////////////////////////////
2175///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2176////////////////////////////////////////////////////////////////////////////////
2177
2178	$('.SF-ShopCategorySlide').owlCarousel({
2179		loop:				true,
2180		margin:				0,
2181		nav:				false,
2182		autoplay: 			false,
2183		dots: 				false,
2184		lazyLoad: 			true,
2185		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-2-sp100 lg-2-sp100 xl-3-sp100')
2186	});
2187	RemoveHandSwipeImg('.SF-ShopCategorySlide');
2188
2189////////////////////////////////////////////////////////////////////////////////
2190///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2191////////////////////////////////////////////////////////////////////////////////
2192
2193	SearchToggler();
2194
2195////////////////////////////////////////////////////////////////////////////////
2196///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2197////////////////////////////////////////////////////////////////////////////////
2198
2199	$('.SlideHome:not(.WeCanDoo)').owlCarousel({
2200		loop:				true,
2201		margin: 			0,
2202		nav:				true,
2203		autoplay: 			true,
2204		autoplayHoverPause:	true,
2205		autoplayTimeout: 	5000,
2206		smartSpeed: 		500,
2207		items:				1,
2208		navText: 			SlideArrows(),
2209		dots: 				false,
2210		lazyLoad: 			true,
2211		onInitialized: function(e)
2212		{
2213			SetAnimatedSlide(e);
2214		},
2215		onTranslated: function(e)
2216		{
2217			SetAnimatedSlide(e);
2218		},
2219		responsive: 		SET_ItemQuantity('xs-1-sp50 sm-1-sp50 md-1-sp0 lg-1-sp0 xl-1-sp0')
2220	});
2221
2222////////////////////////////////////////////////////////////////////////////////
2223///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2224////////////////////////////////////////////////////////////////////////////////
2225
2226	let $LoadMoreBtn 		= $('#LoadMoreItems'),
2227		$Filter				= $('#FilterBlock'),
2228		HasScrollBar		= $Filter.length && $Filter.hasScrollBar() ? $Filter.addClass('HasScrollBar') : '',
2229		LastScrollTop 		= 0,
2230		Processing,
2231		$ImageBlock 		= $('.ProductZoomImg'),
2232		$ProductZone 		= $ImageBlock.parent().find('.ProductZone'),
2233		MainMenuHeight 		= 110,
2234		productZoneBottom 	= $ProductZone.length ? $ProductZone.offset().top + $ProductZone.outerHeight() : 0,
2235		imageBlockTop 		= $ImageBlock.length ? $ImageBlock.offset().top : 0,
2236		imageBlockHeight 	= $ImageBlock.outerHeight(),
2237		windowWidth 		= $(window).width();
2238
2239	$(window).on('scroll touchmove resize',function()
2240	{
2241		var scroll				= $(window).scrollTop(),
2242			FilterWidth			= $Filter.outerWidth(),
2243			IsMobile			= $('.mobile-menu').is(':visible') ? true : false,
2244			FixedFilterTop		= IsMobile ? 60 : 100,
2245			HeaderHeight		= $('#sf__header').height(),
2246			HeadBannerHeight	= $('#page .header-container').height(),
2247			FooterTop			= parseInt($('#ProductsList').outerHeight() - $('#Partners').outerHeight(),10),
2248			H_StickyMenu		= $('#MainMenu.StickyMenu').outerHeight(),
2249			$BuyForm			= $('.SingleProductPage #buy_block'),
2250			$ImgBlock			= $('#image-block'),
2251			ImgBlock 			= $ImgBlock.offset(),
2252			BuyForm 			= $BuyForm.offset(),
2253			H_BuyForm			= $BuyForm.outerHeight(),
2254			$StickyBuyForm		= $('.SingleProductPage #MainMenu #Sticky_buy_Block');
2255		if(scroll !== LastScrollTop)
2256		{
2257			$('.SF_HeaderTheme .mean-container.mobile-menu').addClass('Scrolling');
2258		}
2259		if($(window).width() <= 575)
2260		{
2261			$.doTimeout('scroll', 250, function()
2262			{
2263				$('.SF_HeaderTheme .mean-container.mobile-menu').removeClass('Scrolling');
2264			});
2265			LastScrollTop = scroll;
2266		}
2267		if($BuyForm.length && scroll > BuyForm.top + H_BuyForm - H_StickyMenu)
2268		{
2269			$StickyBuyForm.slideDown(250);
2270		}
2271		else
2272		{
2273			$StickyBuyForm.slideUp(250);
2274		}
2275		if(scroll < HeaderHeight)
2276		{
2277			$('#MainMenu, #CustomMenu').removeClass('StickyMenu');
2278			$('#sf__header').css('margin-bottom','');
2279		}
2280		else
2281		{
2282			$('#MainMenu, #CustomMenu').addClass('StickyMenu');
2283			$('#sf__header').css('margin-bottom','120px');
2284		}
2285		if(Processing)
2286		{
2287			return false;
2288		}
2289		if($(window).scrollTop() >= ($(document).height() - $(window).height())*0.3 && $LoadMoreBtn.length && !$('#FilterBlock input[type="checkbox"]:checked').length)
2290		{
2291			Processing = true;
2292			$('#LoadMoreItems').addClass('Loading');
2293			$.ajax({
2294				type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json", 
2295				data: { LoadMore: true, IdCat: GetProductCatId(), NotIn: GetProductIds(), Blog: IsItBlog(), Filter: FilterSelect() }, cache: false,
2296				success: function(x)
2297				{
2298					setTimeout(function()
2299					{
2300								if(x.Total > 0)
2301								{
2302									Processing = false;
2303									$('#ProductsList').append(x.HTML);
2304									ImagesLoaded();
2305									LoadingEnding();
2306								}
2307								else
2308								{
2309									$LoadMoreBtn.remove();
2310									ImagesLoaded();
2311								}
2312					}, 250);
2313				},
2314				error: function()
2315				{
2316					Processing = false;
2317					ErrorAjax('Une erreur est survenue.');
2318					LoadingEnding();
2319				}
2320			});
2321		}
2322	});
2323
2324////////////////////////////////////////////////////////////////////////////////
2325///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2326////////////////////////////////////////////////////////////////////////////////
2327
2328	$(document).on('change', '#FilterBlock:not(.PrintLabel) input[type="checkbox"]', function(e)
2329	{
2330		$('#FilterBlock input[type="checkbox"]').not(this).prop('checked', false);
2331		var $That 	= $(this).parent(),
2332			$Input 	= $That;
2333		if($Input.is(':checked'))
2334		{
2335			$That.removeClass('load');
2336		}
2337		else
2338		{
2339			$That.addClass('load');
2340			if($LoadMoreBtn.length && $('#FilterBlock:not(.PrintLabel) input[type="checkbox"]:checked').length)
2341			{
2342				$.ajax({
2343					type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json", 
2344					data: { LoadMoreFilter: true, IdCat: GetProductCatId(), NotIn: GetProductIds(), Filter: GetProductFilter(), Blog: IsItBlog(), SearchFilter: FilterSelect() }, cache: false,
2345					success: function(x)
2346					{
2347						Processing = false;
2348						$('#ProductsList').append($(x.HTML));
2349						FilteringItems($That);
2350					},
2351					error: function()
2352					{
2353						Processing = false;
2354						ErrorAjax('Une erreur est survenue.');
2355					}
2356				});
2357			}
2358			else
2359			{
2360				FilteringItems($That);
2361			}
2362		}
2363	}).on('change', '#ContactModal input.UserStatus.radio', function(e)
2364	{
2365		var $ProForm 		= $('#ContactModal fieldset.ProContact'),
2366			$AutoFillInputs	= $('.HideInput'),
2367			$ProInputs		= $ProForm.find('input'),
2368			$BtnSendContact	= $('button.SendContact');
2369		if(this.value == 'Professionnel')
2370		{
2371			$AutoFillInputs.hide();
2372			$BtnSendContact.prop('disabled', true);
2373			$ProForm.fadeIn();
2374			$ProInputs.prop('required', true);
2375		}
2376		else
2377		{
2378			$AutoFillInputs.show();
2379			$BtnSendContact.prop('disabled', false);
2380			$ProForm.fadeOut();
2381			$ProInputs.val('').prop('required', false);
2382		}
2383	}).on('change', '#ConsentPersonnalData', function(e)
2384	{
2385		if($(this).is(':checked'))
2386		{
2387			$(this).closest('.form-item').removeClass('Alert');
2388		}
2389	});
2390
2391////////////////////////////////////////////////////////////////////////////////
2392///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2393////////////////////////////////////////////////////////////////////////////////
2394
2395	$(document).on('keyup', 'form.SearchInStore input', function(e)
2396	{
2397		$(this).parent().find('button[type="submit"]').prop('disabled', (this.value.length > 2 ? false : true));
2398	}).on('keyup', '#FlavorFilter', function(e)
2399	{
2400		FilteringTastes(this);
2401	});
2402
2403////////////////////////////////////////////////////////////////////////////////
2404///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2405////////////////////////////////////////////////////////////////////////////////
2406
2407	$(document).on('submit', 'form.SearchInStore', function(e)
2408	{
2409		e.preventDefault();
2410	
2411		let Searching = $(this)
2412			.find('input[name="SearchStore"]')
2413			.val()
2414			.trim();
2415	
2416		if(IsNotEmpty(Searching))
2417		{
2418			// Encodage URL-safe, mais on garde accents et espaces dans la valeur logique
2419			let Encoded = encodeURIComponent(Searching);
2420	
2421			window.location.href = RootPath() + '/SearchInStore/' + Encoded + '.html';
2422		}
2423	});
2424
2425////////////////////////////////////////////////////////////////////////////////
2426///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2427////////////////////////////////////////////////////////////////////////////////
2428
2429	$(document).on('submit', 'form#FormulaireContact', function(e)
2430	{
2431		var IsValid		= true,
2432			Id 			= this.id,
2433			$Consent	= $('#ConsentPersonnalData');
2434		$('input#prenomContact, input#nomContact, input#emailContact, textarea[name="MessageContact"]').each(function(i)
2435		{
2436			var Value	= $(this).val();
2437			if(IsEmpty(Value) || (this.id === 'emailContact' && !isValidEmailAddress(Value)))
2438			{
2439				$(this).focus();
2440				IsValid = false;
2441				return false;
2442			}
2443		});
2444		let $Message	= $('textarea[name="MessageContact"]'),
2445			Message 	= $('textarea[name="MessageContact"]').val(),
2446			Words		= IsNotEmpty(Message) ? Message.split(' ') : [];
2447		if(Words.length < 5)
2448		{
2449			$Message.closest('.form-item').addClass('Alert').attr('data-message', 'Votre message est trop court.');
2450			$Message.focus();
2451			IsValid = false;
2452			return false;
2453		}
2454		if($Consent.prop('checked') !== true)
2455		{
2456			$Consent.closest('.form-item').addClass('Alert').attr('data-message', 'Veuillez accepter le traitement de vos données');
2457			IsValid = false;
2458			return false;
2459		}
2460		e.preventDefault();
2461		if(IsValid)
2462		{
2463			$('.form-item').removeClass('Alert');
2464			SendForm(Id);
2465		}
2466	});
2467
2468////////////////////////////////////////////////////////////////////////////////
2469///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2470////////////////////////////////////////////////////////////////////////////////
2471
2472	$(document).on('click', 'button.CheckSiret', function(e)
2473	{
2474		let $Siret			= $('#BusinessSiretCodeContact'),
2475			$AutoFillInputs	= $('.HideInput'),
2476			$BtnSendContact	= $('button.SendContact');
2477		$(this).addClass('Loading');
2478		if($Siret.is(':visible'))
2479		{
2480			let SiretNumber 		= $('#BusinessSiretCodeContact').val().replace(/\D/g,''),
2481				CheckSiret 			= new Siret(SiretNumber);
2482			CheckSiret.exists().then(response => {
2483				if(!response)
2484				{
2485					$AutoFillInputs.hide();
2486					$Siret.closest('.form-item').addClass('Alert').attr('data-message', 'Veuillez renseigner un SIRET valide (14 chiffre)');
2487					$BtnSendContact.prop('disabled', true);
2488					$(this).removeClass('Loading');
2489					return false;
2490				}
2491				else
2492				{
2493					let Etablissement	= response.etablissement.unite_legale,
2494						Siege 			= Etablissement.etablissement_siege;
2495					$(this).removeClass('Loading');
2496					$AutoFillInputs.show();
2497					$('#BusinessNameContact').val(IsNotEmpty(Etablissement.denomination) ? Etablissement.denomination : response.etablissement.denomination_usuelle);
2498					$('#BusinessAddressContact').val(Siege.geo_l4);
2499					$('#BusinessZipCodeContact').val(Siege.code_postal);
2500					$('#BusinessTownContact').val(Siege.libelle_commune);
2501					$('#BusinessDateCreatedContact').val(Etablissement.date_creation);
2502					$BtnSendContact.prop('disabled', false);
2503					$Siret.closest('.form-item').removeClass('Alert');
2504				}
2505			});
2506		}
2507	}).on('click', 'a[href*="#"].Anchor', function(e)
2508	{
2509		e.preventDefault();
2510		var Id = $(this).attr('href');
2511		ScrollToAnchor(Id);
2512	});
2513
2514////////////////////////////////////////////////////////////////////////////////
2515///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2516////////////////////////////////////////////////////////////////////////////////
2517
2518	class Siret
2519	{
2520		constructor(siret)
2521		{
2522			this.siret = siret;
2523		}
2524		valid()
2525		{
2526			if(this.siret.length > 13 && Siret.estSiretValide(this.siret))
2527			{
2528				return true;
2529			}
2530			return false;
2531		}
2532		static estSiretValide(siret)
2533		{
2534			if((siret.length != 14) || (isNaN(siret)))
2535				return false;
2536			else
2537			{
2538				let somme = 0;
2539				let tmp;
2540				for(let cpt = 0; cpt < siret.length; cpt++)
2541				{
2542					if((cpt % 2) == 0)
2543					{
2544						tmp = siret.charAt(cpt) * 2; 
2545						if(tmp > 9)
2546							tmp -= 9;
2547					} else
2548						tmp = siret.charAt(cpt);
2549					somme += parseInt(tmp);
2550				}
2551				if((somme % 10) !== 0) return false;
2552			}
2553			return true;
2554		}
2555		convert() {
2556			let siren = this.siret.substring(0, 9);
2557			let tva = (12 + 3 * (siren % 97)) % 97;
2558			return "FR" + tva + siren;
2559		}
2560		async exists(){
2561			var myHeaders = new Headers();
2562				myHeaders.append('Accept', '*/*');
2563				myHeaders.append('Content-Type', 'application/json');
2564				myHeaders.append('Access-Control-Allow-Origin', '*');
2565
2566			let response = 	await fetch(`https://entreprise.data.gouv.fr/api/sirene/v3/etablissements/${this.siret}`,
2567									{
2568//										mode: 		'no-cors',
2569										method: 	'GET',
2570										headers:	myHeaders
2571							});
2572			let data = await response.json();
2573			if(data.etablissement === undefined){
2574				return false;
2575			}
2576			return data;
2577		}
2578	}
2579
2580////////////////////////////////////////////////////////////////////////////////
2581///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2582////////////////////////////////////////////////////////////////////////////////
2583
2584	$(document).on('click', '.snipcart-item-quantity__quantity button, .snipcart__item__line__header__actions', function(e)
2585	{
2586		$(this).closest('.snipcart__item__line__product').addClass('Loading');
2587	});
2588
2589////////////////////////////////////////////////////////////////////////////////
2590///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2591////////////////////////////////////////////////////////////////////////////////
2592
2593	document.addEventListener('snipcart.ready', () =>
2594	{
2595		const CATEGORIES 		= ['Carte cadeau', 'Atelier', 'Atelier privé'];
2596		const DEFERRED_PAYMENT 	= 'deferred_payment';
2597		window.filterPaymentMethods = function(paymentMethod)
2598		{
2599			const cartHasItem = Snipcart.store.getState().cart.items.items.some((e) => CATEGORIES.includes(e.categories[0]));
2600			if(paymentMethod.id === DEFERRED_PAYMENT && cartHasItem)
2601			{
2602				return false;
2603			}
2604			return true;
2605		};
2606
2607		////////////////////////////////////////////////////////////////////////////////
2608		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2609		////////////////////////////////////////////////////////////////////////////////
2610
2611		Snipcart.api.session.setLanguage('fr',
2612		{
2613			'actions': 
2614			{
2615				'dismiss': 'Non merci',
2616				'apply_changes': 'Appliquer les modifications',
2617				'yes_use_it': 'Oui, utilisez-le',
2618				'save_changes': 'Enregistrer les modifications',
2619				'back_to_orders': 'Retour aux commandes',
2620				'change_password': 'Changer le mot de passe',
2621				'clear_cart': 'Vider le panier',
2622				'add': 'Ajouter',
2623				'checkout': 'Commander',
2624				'continue_shopping': 'Continuer vos achats',
2625			},
2626			'header':
2627			{
2628				'title_cart_summary': 'Votre panier',
2629				'loading': 'Chargement…'
2630			},
2631			'cart_summary':
2632			{
2633				'taxes': 'TVA',
2634				'taxes_included_in_price': 'Certains produits sont soumis à la TVA.',
2635			},
2636			'register_form':
2637			{
2638				'requires_action': 
2639				{
2640					'title': 'Vérifiez vos emails',
2641					'description': 'Ce courriel a déjà été utilisé lors d’une commande. Veuillez compléter le processus d’inscription en utilisant le lien qui vous a été envoyé par courriel.'
2642				}
2643			},
2644			'discount_box': {
2645				'promo_code': 'Vous avez un code promo ou\u00A0carte\u00A0cadeau\u00A0?',
2646				'promo_code_placeholder': 'Saisissez votre code',
2647				'promocode_applied': 'Promotion appliquée'
2648			},
2649			'reset_password_form':
2650			{
2651				'title': 'Réinitialisez votre mot de passe',
2652				'password': 'Nouveau mot de passe',
2653				'confirmation_password': 'Entrez à nouveau votre nouveau mot de passe',
2654				'action': 'Réinitialiser le mot de passe'
2655			},
2656			'forgot_password_form':
2657			{
2658				'title': 'Récupérer votre mot de passe',
2659				'instructions': 'Entrez l’adresse courriel de votre compte et nous vous enverrons un lien de réinitialisation de mot de passe.',
2660				'email': 'Courriel',
2661				'email_instructions': 'Un courriel contenant un lien vous a été envoyé. Cliquez dessus pour réinitialiser votre mot de passe.',
2662				'action': 'Envoyer un courriel de réinitialisation du mot de passe'
2663			},
2664			'change_password_form': 
2665			{
2666				'title': 'Changez votre mot de passe',
2667				'current_password': 'Mot de passe actuel',
2668				'password': 'Nouveau mot de passe',
2669				'confirmation_password': 'Entrez à nouveau votre nouveau mot de passe',
2670				'action': 'Changer le mot de passe'
2671			},
2672			'customer_details': 
2673			{
2674				'title': 'Détails du compte'
2675			},
2676			'customer_dashboard': 
2677			{
2678				'orders': 'Commandes',
2679				'loading': 'Chargement...',
2680			},
2681			'customer_orders':
2682			{
2683				'loading': 'Chargement...'
2684			},
2685			'address_form': {
2686				'firstName': 'Prénom',
2687				'lastName': 'Nom',
2688			},
2689			'default': 
2690			{
2691				'loading': 'Chargement...',
2692				'error': 'Une erreur s’est produite.',
2693				'success': 'Succès !'
2694			},
2695			'signin_form': 
2696			{
2697				'signin': 'Connexion',
2698				'dont_have_an_account': 'Pas de compte ?',
2699				'email': 'Courriel',
2700				'password': 'Mot de passe',
2701				'forgot_your_password': 'Mot de passe oublié ?',
2702				'close_form': 'Retour'
2703			},
2704			 'cart': 
2705			 {
2706				'view_detailed_cart': 'Voir le panier détaillé',
2707				'summary': 'Résumé de votre panier',
2708			},
2709			'shipping_rates': 
2710			{
2711				'guaranteed_delivery_days': 'Délai de livraison estimé : %{guaranteedDaysToDelivery} jour(s).'
2712			},
2713			'shippingRates':
2714			{
2715				'loading': 'Chargement des frais de port'
2716			},
2717			'payment':
2718			{
2719				'form': 
2720				{
2721					'invalid_number': 'Votre numéro de carte est invalide',
2722					'invalid_expiration': 'Date d’expiration invalide',
2723					'invalid_cvv': 'CVV invalide',
2724					'deferred_payment_title': 'Payer par virement bancaire ou chèque',
2725					'deferred_payment_instructions': '<style>h3 {color: #536d30;margin: 5px 0 2px 0;font-weight: bold;font-family: sans-serif;text-transform: uppercase;font-size: 12px;border-bottom: 1px solid  #536d30;padding: 0 0 2px 0; } p { font-size: 12px; padding: 0 10px; margin-bottom: 10px } p span { display: block; font-size: 12px; margin-bottom: 10px } b {font-family: sans-serif;font-size: inherit; } </style><span>En passant cette commande, vous acceptez de payer par chèque ou virement.</span><h3>Chèque</h3><span>À l’ordre de <b>Sacré français</b> dans un délai de 15 jours. Adressez votre paiement par courrier postal à <b>Sacré français - MIN De Brienne - 110, Quai Paludate, Ent. 4, Bat. 2 - 33800 Bordeaux - France </b></span><h3>Virement bancaire</h3><span><b>Banque : </b> 15589 <b>Guichet : </b> 33558 <b>N° de compte : </b> 07259597843 <b>Clé : </b>66<br><b>IBAN : </b>FR76 1558 9335 5807 7551 6804 036 <b>BIC : </b>CMBRFR2BXXX</span><span class="mb-4">Votre commande sera envoyée <u>après encaissement</u> de votre paiement.</span>'
2726				},
2727				'methods': 
2728				{
2729					'deferred_payment': 'Payer par virement bancaire ou chèque'
2730				},
2731				'checkout_with': 'Payer avec'
2732			},
2733			'errors':
2734			{
2735				'promo_code_is_invalid': 'Ce code promo/carte cadeau n’est pas valide',
2736				'order_validation':
2737				{
2738					'domain_crawling': {
2739						'title':'Oups ! Votre commande n’a pas pu être complétée.',
2740						'description':'La disponibilité des produits de votre panier n’a pas pu être confirmée.'+SET_OrderErrorMessage('domain_crawling')
2741					},
2742					'product_crawling': {
2743						'title':'Aïe ! Le prix de produits dans votre panier semble avoir changé.',
2744						'description': 'Essayez de les enlever et de les remettre dans votre panier.'+SET_OrderErrorMessage()
2745					},
2746					'stock_validation': {
2747						'title':'Oooh ! Produit en rupture de stock.',
2748						'description': 'Quelques produits semblent être en rupture de stock. Veuillez réviser les quantités.'+SET_OrderErrorMessage()
2749					},
2750					'custom_fields_validation': {
2751						'title': 'Information requise manquante.',
2752						'description': 'Il manque des informations obligatoires pour certains produits du panier. Veuillez corriger le panier pour continuer.'
2753					},
2754					'shipping_validation': {
2755						'title': 'La méthode de livraison n’est pas valide.',
2756						'description': 'La méthode de livraison sélectionnée semble avoir été modifiée ou n’est plus disponible.'+SET_OrderErrorMessage()
2757					}
2758				}
2759			}
2760		});
2761
2762		////////////////////////////////////////////////////////////////////////////////
2763		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2764		////////////////////////////////////////////////////////////////////////////////
2765
2766		Snipcart.api.theme.customization.registerPaymentFormCustomization({
2767			input: {
2768				backgroundColor: 'rgb(83 109 48 / 5%)',
2769				color: '#303030',
2770				border: '1px solid rgb(83 109 48 / 15%)',
2771				fontSize: '16px',
2772				placeholder: {
2773					color: '#536d30',
2774				},
2775			},
2776			label:
2777			{
2778				color: '#536d30',
2779				fontSize: '20px'
2780			}
2781		});
2782
2783		////////////////////////////////////////////////////////////////////////////////
2784		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2785		////////////////////////////////////////////////////////////////////////////////
2786
2787		Snipcart.events.on('snipcart.initialized', (snipcartState) => {
2788			$(document).on('click', 'button.snipcart-payment-methods-list-item__button', function(e)
2789			{
2790				$(this).addClass('active');
2791			});
2792		});
2793
2794		////////////////////////////////////////////////////////////////////////////////
2795		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2796		////////////////////////////////////////////////////////////////////////////////
2797
2798		Snipcart.events.on('item.adding', (parsedCartItem) => {
2799			$('#ProductModal').modal('hide');
2800			SetHtmlTagToOverflowHidden(false);
2801			SetFreeShipping(Snipcart);
2802			UpdateViewedProduct(parsedCartItem.id, 'Add');
2803		});
2804
2805		////////////////////////////////////////////////////////////////////////////////
2806		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2807		////////////////////////////////////////////////////////////////////////////////
2808
2809		Snipcart.events.on('item.updated', (cartItem) => {
2810			$('.snipcart__item__line__product').removeClass('Loading');
2811			SetFreeShipping(Snipcart);
2812		});
2813
2814		////////////////////////////////////////////////////////////////////////////////
2815		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2816		////////////////////////////////////////////////////////////////////////////////
2817
2818		Snipcart.events.on('item.removed', (cartItem) => {
2819			SetFreeShipping(Snipcart);
2820			UpdateViewedProduct(cartItem.id, 'Remove');
2821		});
2822
2823		////////////////////////////////////////////////////////////////////////////////
2824		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2825		////////////////////////////////////////////////////////////////////////////////
2826
2827//		Snipcart.events.on('summary.checkout_clicked', () => {
2828//			SetFreeShipping(Snipcart);
2829//		});
2830
2831		////////////////////////////////////////////////////////////////////////////////
2832		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2833		////////////////////////////////////////////////////////////////////////////////
2834
2835		Snipcart.events.on('customer.signedin', (customer) => {
2836			setTimeout(function()
2837			{
2838				window.location.replace('#/signin','#/dashboard');
2839			}, 250);
2840		});
2841
2842		////////////////////////////////////////////////////////////////////////////////
2843		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2844		////////////////////////////////////////////////////////////////////////////////
2845
2846		Snipcart.events.on('cart.confirm.error', (confirmError) => {
2847			StoreLogsOnError(confirmError, Snipcart.store.getState());
2848		});
2849
2850		////////////////////////////////////////////////////////////////////////////////
2851		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2852		////////////////////////////////////////////////////////////////////////////////
2853
2854		Snipcart.events.on('payment.failed', (paymentError) => {
2855			StoreLogsOnError(paymentError, Snipcart.store.getState());
2856		});
2857
2858		////////////////////////////////////////////////////////////////////////////////
2859		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2860		////////////////////////////////////////////////////////////////////////////////
2861
2862		Snipcart.events.on('theme.routechanged', (routesChange) => {
2863			$('#snipcart').attr('data-snipcart-page', routesChange.to);
2864			if(routesChange.to === '/checkout')
2865			{
2866				StickyPaymentStepsButtons('routechanged');
2867				CheckCartBeforeCheckout();
2868
2869				$(document).on('click', '.RemoveCartItem', function(e)
2870				{
2871					e.preventDefault();
2872					var $That		= $(this),
2873						itemId 		= $That.attr('data-id');
2874					$That.prop('disabled', true);
2875					$That.closest('.snipcart-cart-summary-item').addClass('Loading');
2876					setTimeout(function()
2877					{
2878						Snipcart.api.cart.items.remove(itemId);
2879					}, 1000);
2880				});
2881				$(document).on('click', '.ChangeQuantity', function(e)
2882				{
2883					e.preventDefault();
2884					var $That			= $(this),
2885						itemId 			= $That.attr('data-id'),
2886						action 			= $That.attr('data-action');
2887					$That.prop('disabled', true);
2888					$That.closest('.snipcart-cart-summary-item').addClass('Loading');
2889					setTimeout(function()
2890					{
2891						var items 				= Snipcart.store.getState().cart.items.items, 
2892							currentQuantity 	= 0;
2893						for(var i = 0; i < items.length; i++)
2894						{
2895							if(items[i].uniqueId === itemId)
2896							{
2897								currentQuantity = items[i].quantity;
2898								break;
2899							}
2900						}
2901						var  quantityChange 	= action === 'increase' ? currentQuantity+1 : currentQuantity-1
2902						if(quantityChange > 0)
2903						{
2904							Snipcart.api.cart.items.update({uniqueId:itemId, quantity:quantityChange});
2905						}
2906					}, 500);
2907				});
2908			}
2909		});
2910
2911		////////////////////////////////////////////////////////////////////////////////
2912		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2913		////////////////////////////////////////////////////////////////////////////////
2914
2915		Snipcart.events.on('item.updated', (cartItem) => {
2916			$('.ChangeQuantity').closest('.snipcart-cart-summary-item').removeClass('Loading');
2917			$('.ChangeQuantity').prop('disabled', false);
2918		});
2919
2920		////////////////////////////////////////////////////////////////////////////////
2921		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2922		////////////////////////////////////////////////////////////////////////////////
2923
2924		Snipcart.events.on('shipping.selected', (shippingMethod) => {
2925			StickyPaymentStepsButtons('shipping.selected');
2926		});
2927
2928		////////////////////////////////////////////////////////////////////////////////
2929		///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2930		////////////////////////////////////////////////////////////////////////////////
2931
2932		StickyPaymentStepsButtons('snipcart ready');
2933	});
2934
2935////////////////////////////////////////////////////////////////////////////////
2936///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2937////////////////////////////////////////////////////////////////////////////////
2938
2939	$(document).on('click', '.ProductAttribute .BtnQty', function(e)
2940	{
2941		e.preventDefault();
2942		var IsModal = $('#ProductModal').hasClass('show') ? true : false,
2943			$Qty	= IsModal ? $('.Modal.ProductAttribute .AddMoreBtn .ProductQty') : $(this).parent().find('.ProductQty'), //$('.Page.ProductAttribute .AddMoreBtn .ProductQty'),
2944			Max		= $Qty.attr('max'),
2945			Count 	= $Qty.val(),
2946			Data 	= $(this).attr('data-qty') === 'Plus' ? Count++ : (Count > 1 ? Count-- : 0);
2947		if(IsNotEmpty(Max) && Count >= Max)
2948		{
2949			Count	= Max;
2950		}
2951		$Qty.val(Count);
2952		$Qty.closest('form').find('.snipcart-add-item:not(.LimitedQty)[data-item-quantity]').attr('data-item-quantity', Count);
2953		$Qty.closest('form').find('.snipcart-add-item .TotalWorkShop').text(Count+' place'+(Count > 1 ? 's' : ''));
2954	});
2955
2956////////////////////////////////////////////////////////////////////////////////
2957///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2958////////////////////////////////////////////////////////////////////////////////
2959
2960	$(document).on('click','ul#MoreInfoTabs li a',function(e)
2961	{
2962		e.preventDefault();
2963		var Id = $(this).attr('href');
2964		$('*[id^="InfoTab"]').addClass('HideMoreInfoSection');
2965		$('section'+Id).removeClass('HideMoreInfoSection');
2966		$('ul#MoreInfoTabs a[href^="#InfoTab"]').removeClass('Active');
2967		$(this).addClass('Active');
2968	});
2969
2970////////////////////////////////////////////////////////////////////////////////
2971///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2972////////////////////////////////////////////////////////////////////////////////
2973
2974	$(document).on('click','.mean-container .mean-nav ul:not(.Short) li a',function(e)
2975	{
2976		$('.mean-container .mean-nav ul:not(.Short) li a').parent().removeClass('NearActive');
2977		$(this).parent().addClass('NearActive');
2978	});
2979
2980////////////////////////////////////////////////////////////////////////////////
2981///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2982////////////////////////////////////////////////////////////////////////////////
2983
2984	$('.dropdown').parent('.drop').css('position' , 'relative');
2985
2986////////////////////////////////////////////////////////////////////////////////
2987///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
2988////////////////////////////////////////////////////////////////////////////////
2989
2990	$(document).on('click', '[data-idproduct]', function(e)
2991	{
2992		var IdProduct 	= $(this).data('idproduct'),
2993			$Modal 		= $('#ProductModal'),
2994			Link 		= $(this).closest('.ProductThumb').find('.FirstImg').attr('href'),
2995			LinkText	= IsNotEmpty(Link) ? '<br>Vous pouvez néanmoins <a class="stretched-link text-danger" href="'+Link+'">accéder à la page de ce produit en suivant ce lien</a>.' : '';
2996		$.ajax(
2997		{
2998			type: 'POST', url: RootPath()+'/Ajax.php', dataType: 'json',
2999			data: { GetProduct: IdProduct },
3000			cache: false,
3001			success: function(Retour)
3002			{
3003				if(IsNotEmpty(Retour.HTML_ProductDetail))
3004				{
3005					$Modal.find('.modal-product').html(Retour.HTML_ProductDetail);
3006					UpdateCartFunctions();
3007//					SetFreeShipping(Snipcart);
3008					ImagesLoaded();
3009					SetHtmlTagToOverflowHidden(true);
3010					StoreViewedProduct();
3011				}
3012				else
3013				{
3014					$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'+LinkText));
3015				}
3016			},
3017			error: function(xhr, status, error)
3018			{
3019				$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'+LinkText));
3020			}
3021		});
3022	});
3023
3024////////////////////////////////////////////////////////////////////////////////
3025///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3026////////////////////////////////////////////////////////////////////////////////
3027
3028	$(document).on('click', '[data-idblog]', function(e)
3029	{
3030		var IdBlog	 	= $(this).data('idblog'),
3031			$Modal 		= $('#ProductModal'),
3032			Link 		= $(this).closest('.ProductThumb').find('.FirstImg').attr('href'),
3033			LinkText	= IsNotEmpty(Link) ? '<br>Vous pouvez néanmoins <a class="stretched-link text-danger" href="'+Link+'">accéder à cette page en suivant ce lien</a>.' : '';
3034		$.ajax(
3035		{
3036			type: 'POST', url: RootPath()+'/Ajax.php', dataType: 'json',
3037			data: { GetBlog: IdBlog },
3038			cache: false,
3039			success: function(Retour)
3040			{
3041				if(IsNotEmpty(Retour.HTML_BlogDetail))
3042				{
3043					$Modal.find('.modal-product').html(Retour.HTML_BlogDetail);
3044					UpdateCartFunctions();
3045					ImagesLoaded();
3046					SetHtmlTagToOverflowHidden(true);
3047				}
3048				else
3049				{
3050					$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'+LinkText));
3051				}
3052			},
3053			error: function(xhr, status, error)
3054			{
3055				$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'+LinkText));
3056			}
3057		});
3058	});
3059
3060////////////////////////////////////////////////////////////////////////////////
3061///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3062////////////////////////////////////////////////////////////////////////////////
3063
3064	$(document).on('click', '.TranslationFlags a', function()
3065	{
3066		var Lang 			= $(this).data('lang');
3067		var $LangSelector 	= $('.goog-te-combo');
3068		var db = $LangSelector.get(0);
3069		$LangSelector.val(Lang);
3070		FireEvent(db, 'change');
3071		document.cookie = 'googtrans=/fr/'+Lang+'';
3072		setTimeout(function()
3073		{
3074			UpdateLanguageFlag();
3075		}, 250);
3076	});
3077
3078////////////////////////////////////////////////////////////////////////////////
3079///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3080////////////////////////////////////////////////////////////////////////////////
3081
3082	$(document).on('click', '[data-idmarket]', function(e)
3083	{
3084		var IdMarket 	= $(this).data('idmarket'),
3085			$Modal 		= $('#MarketModal');
3086		$.ajax(
3087		{
3088			type: 'POST', url: RootPath()+'/Ajax.php', dataType: 'json',
3089			data: { GetMarket: IdMarket },
3090			cache: false,
3091			success: function(Retour)
3092			{
3093				if(IsNotEmpty(Retour.HTML_MarketDetail))
3094				{
3095					$Modal.find('.modal-product').html(Retour.HTML_MarketDetail);
3096					UpdateCartFunctions();
3097					ImagesLoaded();
3098				}
3099				else
3100				{
3101					$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'));
3102				}
3103			},
3104			error: function(xhr, status, error)
3105			{
3106				$Modal.find('.modal-product').html(HTML_AjaxAlert('Une erreur est survenue. Nous mettons tout en œuvre pour la corriger.'));
3107			}
3108		});
3109	});
3110
3111////////////////////////////////////////////////////////////////////////////////
3112///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3113////////////////////////////////////////////////////////////////////////////////
3114
3115	$(document).on('submit', 'form#ProLoginForm', function(e)
3116	{
3117		e.preventDefault();
3118		let LoginForm = new FormData(this);
3119		$.ajax({
3120			type: 'POST', url: RootPath()+'/Ajax.php', dataType: "json",
3121			data: LoginForm,
3122			cache: false,
3123			contentType: false,
3124			processData: false,
3125			success: function(x)
3126			{
3127				if(x.hasOwnProperty('Error'))
3128				{
3129					ErrorLoginAjax(x['Error']);
3130				}
3131				else
3132				{
3133					location.reload();
3134				}
3135			},
3136			error: function()
3137			{
3138				ErrorLoginAjax('Une erreur est survenue.');
3139			}
3140		});
3141	});
3142
3143////////////////////////////////////////////////////////////////////////////////
3144///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3145////////////////////////////////////////////////////////////////////////////////
3146
3147	$(document).on('click', '.RevealPassWord', function(e)
3148	{
3149		let $Input	= $(this).parent().find('input');
3150		console.log($Input.type);
3151		if($Input.attr('type') === 'password')
3152		{
3153			$Input.attr('type', 'text');
3154			$(this).toggleClass('active');
3155		}
3156		else
3157		{
3158			$Input.attr('type', 'password');
3159		}
3160	});
3161
3162////////////////////////////////////////////////////////////////////////////////
3163///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3164////////////////////////////////////////////////////////////////////////////////
3165
3166	$(document).on('click', '.ScrollToReviews', function(e)
3167	{
3168		e.preventDefault();
3169		ScrollToAnchor('#MoreInfoTabs');
3170		$('#MoreInfoTabs').find('.Active').removeClass('Active');
3171		$('#MoreInfoTab_Reviews').addClass('Active');
3172		$('#MoreInfoTabs').parent().find('section').addClass('HideMoreInfoSection');
3173		$('#InfoTabReviews').removeClass('HideMoreInfoSection');
3174	});
3175
3176////////////////////////////////////////////////////////////////////////////////
3177///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3178////////////////////////////////////////////////////////////////////////////////
3179
3180	$(document).ready(function()
3181	{
3182		UpdateCartFunctions();
3183		ImagesLoaded();
3184		ScrollToAnchor();
3185		CloneBuyForm();
3186		UpdateLanguageFlag();
3187		CheckResponsiveTable();
3188		ResetHtmlTagOverflow();
3189		DisplayViewedProducts();
3190		StoreViewedProduct();
3191		GetUniqueReviews('');
3192		GetAsideContent();
3193
3194	});
3195})(jQuery);
3196
3197////////////////////////////////////////////////////////////////////////////////
3198///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3199////////////////////////////////////////////////////////////////////////////////
3200
3201	function ManageCookies(Status)
3202	{
3203		if(Status === 'deny')
3204		{
3205			var theCookies = document.cookie.split(';');
3206			const AuthorizedCookies = ['snipcart-cart', '_ga', 'googtrans', 'SacreFrancais'];
3207			for(var i = 0 ; i < theCookies.length; i++)
3208			{
3209				var CookieName = theCookies[i].split('=')[0].replace(' ','');
3210				if(!AuthorizedCookies.includes(CookieName))
3211				{
3212					document.cookie = theCookies[i].split('=')[0] + '=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
3213				}
3214			}
3215		}
3216	}
3217
3218////////////////////////////////////////////////////////////////////////////////
3219///////  ///////  ///////  ///////  ///////  ///////  ///////  ///////  ////////
3220////////////////////////////////////////////////////////////////////////////////
3221
3222	if($('#CookieConsentBox').length)
3223	{
3224		new window.cookieconsent.initialise(
3225		{
3226			//https://github.com/osano/cookieconsent#version-31
3227			cookie: { 	name: 'SacreFrancais', 
3228						domain: 'www.SacreFrancais.fr'
3229			}, 
3230			theme: 'edgeless', 
3231			content: { 	header:		'Cookies used on the website!',
3232						message: 	'Sacré français est une boutique en ligne respectueuse de votre vie privée. Néanmoins, son fonctionnement nécessite le <b><u>dépôt de cookies essentiels</u></b> :<br><span class="FakeBtn"></span> <b>Snipcart</b> permet la gestion du panier. <a class="MoreInfo" href="https://snipcart.com/fr" target="_blank">+ d’infos</a><br><span class="FakeBtn"></span> <b>Google Analytics</b> permet d’analyser le trafic et d’optimiser votre expérience de navigation<br><span class="FakeBtn"></span> <b>Google Translate</b> permet la traduction du site.<br><span class="FakeBtn"></span> <b>Société des avis garantisés</b> permet d\'afficher les notes des produits.<hr class="my-1" >D’autres <b>cookies d’agrément, non-essentiels,</b> peuvent être déposés par : <br><span class="FakeBtn active"></span> Les <b>réseaux sociaux</b> (Facebook, …),  les <b>hébergeurs de contenu</b> (Youtube, …), les partenaires…', 
3233						dismiss: 	'Fermer', 
3234						allow:		'J’accepte le dépôt de cookies',
3235						deny: 		'Je refuse les cookies non-essentiels',
3236						close: 		'&#x274c;',
3237						link: 		'En savoir plus', 
3238						policy: 	'Cookies',
3239						href: 		'https://www.SacreFrancais.fr/cgv.php'}, 
3240			revokable:true,
3241			law: { regionalLaw: false },
3242			location: true,
3243			type: 'opt-out', 
3244			onInitialise: function(status)
3245			{
3246				var type 		= this.options.type,
3247					didConsent 	= this.hasConsented();
3248				if(type == 'opt-out' && !didConsent)
3249				{
3250					ManageCookies(status);
3251				}
3252			},
3253			onStatusChange: function(status, chosenBefore)
3254			{
3255				var type 		= this.options.type,
3256					didConsent 	= this.hasConsented();
3257				if(type == 'opt-out' && !didConsent)
3258				{
3259					ManageCookies(status);
3260				}
3261			},
3262			onRevokeChoice: function()
3263			{
3264				var type 		= this.options.type;
3265				if(type == 'opt-out')
3266				{
3267					ManageCookies(status);
3268				}
3269			}
3270		});
3271	}

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.