PageSourceSearch

https://openline-vitaly.com/wp-content/themes/parsek/js/libs/cookie-banner/silktide-consent-manager.js

js openline-vitaly.com collected 2026-10-06 13:11:39 UTC 30,855 bytes, 877 lines download raw bytes

1// Silktide Consent Manager - https://silktide.com/consent-manager/
2
3class SilktideCookieBanner {
4	constructor(config) {
5		this.config = config; // Save config to the instance
6
7		this.wrapper = null;
8		this.banner = null;
9		this.modal = null;
10		this.cookieIcon = null;
11		this.backdrop = null;
12
13		this.createWrapper();
14
15		if (this.shouldShowBackdrop()) {
16			this.createBackdrop();
17		}
18
19		this.createCookieIcon();
20		this.createModal();
21
22		if (this.shouldShowBanner()) {
23			this.createBanner();
24			this.showBackdrop();
25		} else {
26			this.showCookieIcon();
27		}
28
29		this.setupEventListeners();
30
31		if (this.hasSetInitialCookieChoices()) {
32			this.loadRequiredCookies();
33			this.runAcceptedCookieCallbacks();
34		}
35	}
36
37	destroyCookieBanner() {
38		// Remove all cookie banner elements from the DOM
39		if (this.wrapper && this.wrapper.parentNode) {
40			this.wrapper.parentNode.removeChild(this.wrapper);
41		}
42
43		// Restore scrolling
44		this.allowBodyScroll();
45
46		// Clear all references
47		this.wrapper = null;
48		this.banner = null;
49		this.modal = null;
50		this.cookieIcon = null;
51		this.backdrop = null;
52	}
53
54	// ----------------------------------------------------------------
55	// Wrapper
56	// ----------------------------------------------------------------
57	createWrapper() {
58		this.wrapper = document.createElement("div");
59		this.wrapper.id = "silktide-wrapper";
60		document.body.insertBefore(this.wrapper, document.body.firstChild);
61	}
62
63	// ----------------------------------------------------------------
64	// Wrapper Child Generator
65	// ----------------------------------------------------------------
66	createWrapperChild(htmlContent, id) {
67		// Create child element
68		const child = document.createElement("div");
69		child.id = id;
70		child.innerHTML = htmlContent;
71
72		// Ensure wrapper exists
73		if (!this.wrapper || !document.body.contains(this.wrapper)) {
74			this.createWrapper();
75		}
76
77		// Append child to wrapper
78		this.wrapper.appendChild(child);
79		return child;
80	}
81
82	// ----------------------------------------------------------------
83	// Backdrop
84	// ----------------------------------------------------------------
85	createBackdrop() {
86		this.backdrop = this.createWrapperChild(null, "silktide-backdrop");
87	}
88
89	showBackdrop() {
90		if (this.backdrop) {
91			this.backdrop.style.display = "block";
92		}
93		// Trigger optional onBackdropOpen callback
94		if (typeof this.config.onBackdropOpen === "function") {
95			this.config.onBackdropOpen();
96		}
97	}
98
99	hideBackdrop() {
100		if (this.backdrop) {
101			this.backdrop.style.display = "none";
102		}
103
104		// Trigger optional onBackdropClose callback
105		if (typeof this.config.onBackdropClose === "function") {
106			this.config.onBackdropClose();
107		}
108	}
109
110	shouldShowBackdrop() {
111		return this.config?.background?.showBackground || false;
112	}
113
114	// update the checkboxes in the modal with the values from localStorage
115	updateCheckboxState(saveToStorage = false) {
116		const preferencesSection = this.modal.querySelector("#cookie-preferences");
117		const checkboxes = preferencesSection.querySelectorAll('input[type="checkbox"]');
118
119		checkboxes.forEach((checkbox) => {
120			const [, cookieId] = checkbox.id.split("cookies-");
121			const cookieType = this.config.cookieTypes.find((type) => type.id === cookieId);
122
123			if (!cookieType) return;
124
125			if (saveToStorage) {
126				// Save the current state to localStorage and run callbacks
127				const currentState = checkbox.checked;
128
129				if (cookieType.required) {
130					localStorage.setItem(`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`, "true");
131				} else {
132					localStorage.setItem(
133						`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`,
134						currentState.toString()
135					);
136
137					// Run appropriate callback
138					if (currentState && typeof cookieType.onAccept === "function") {
139						cookieType.onAccept();
140					} else if (!currentState && typeof cookieType.onReject === "function") {
141						cookieType.onReject();
142					}
143				}
144			} else {
145				// When reading values (opening modal)
146				if (cookieType.required) {
147					checkbox.checked = true;
148					checkbox.disabled = true;
149				} else {
150					const storedValue = localStorage.getItem(
151						`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`
152					);
153
154					if (storedValue !== null) {
155						checkbox.checked = storedValue === "true";
156					} else {
157						checkbox.checked = !!cookieType.defaultValue;
158					}
159				}
160			}
161		});
162	}
163
164	setInitialCookieChoiceMade() {
165		window.localStorage.setItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`, 1);
166	}
167
168	// ----------------------------------------------------------------
169	// Consent Handling
170	// ----------------------------------------------------------------
171	handleCookieChoice(accepted) {
172		// We set that an initial choice was made regardless of what it was so we don't show the banner again
173		this.setInitialCookieChoiceMade();
174
175		this.removeBanner();
176		this.hideBackdrop();
177		this.toggleModal(false);
178		this.showCookieIcon();
179
180		this.config.cookieTypes.forEach((type) => {
181			// Set localStorage and run accept/reject callbacks
182			if (type.required == true) {
183				localStorage.setItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`, "true");
184				if (typeof type.onAccept === "function") {
185					type.onAccept();
186				}
187			} else {
188				localStorage.setItem(
189					`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
190					accepted.toString()
191				);
192
193				if (accepted) {
194					if (typeof type.onAccept === "function") {
195						type.onAccept();
196					}
197				} else {
198					if (typeof type.onReject === "function") {
199						type.onReject();
200					}
201				}
202			}
203		});
204
205		// Trigger optional onAcceptAll/onRejectAll callbacks
206		if (accepted && typeof this.config.onAcceptAll === "function") {
207			if (typeof this.config.onAcceptAll === "function") {
208				this.config.onAcceptAll();
209			}
210		} else if (typeof this.config.onRejectAll === "function") {
211			if (typeof this.config.onRejectAll === "function") {
212				this.config.onRejectAll();
213			}
214		}
215
216		// finally update the checkboxes in the modal with the values from localStorage
217		this.updateCheckboxState();
218	}
219
220	getAcceptedCookies() {
221		return (this.config.cookieTypes || []).reduce((acc, cookieType) => {
222			acc[cookieType.id] =
223				localStorage.getItem(`silktideCookieChoice_${cookieType.id}${this.getBannerSuffix()}`) ===
224				"true";
225			return acc;
226		}, {});
227	}
228
229	runAcceptedCookieCallbacks() {
230		if (!this.config.cookieTypes) return;
231
232		const acceptedCookies = this.getAcceptedCookies();
233		this.config.cookieTypes.forEach((type) => {
234			if (type.required) return; // we run required cookies separately in loadRequiredCookies
235			if (acceptedCookies[type.id] && typeof type.onAccept === "function") {
236				if (typeof type.onAccept === "function") {
237					type.onAccept();
238				}
239			}
240		});
241	}
242
243	runRejectedCookieCallbacks() {
244		if (!this.config.cookieTypes) return;
245
246		const rejectedCookies = this.getRejectedCookies();
247		this.config.cookieTypes.forEach((type) => {
248			if (rejectedCookies[type.id] && typeof type.onReject === "function") {
249				if (typeof type.onReject === "function") {
250					type.onReject();
251				}
252			}
253		});
254	}
255
256	/**
257	 * Run through all of the cookie callbacks based on the current localStorage values
258	 */
259	runStoredCookiePreferenceCallbacks() {
260		this.config.cookieTypes.forEach((type) => {
261			const accepted =
262				localStorage.getItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`) === "true";
263			// Set localStorage and run accept/reject callbacks
264			if (accepted) {
265				if (typeof type.onAccept === "function") {
266					type.onAccept();
267				}
268			} else {
269				if (typeof type.onReject === "function") {
270					type.onReject();
271				}
272			}
273		});
274	}
275
276	loadRequiredCookies() {
277		if (!this.config.cookieTypes) return;
278		this.config.cookieTypes.forEach((cookie) => {
279			if (cookie.required && typeof cookie.onAccept === "function") {
280				if (typeof cookie.onAccept === "function") {
281					cookie.onAccept();
282				}
283			}
284		});
285	}
286
287	// ----------------------------------------------------------------
288	// Banner
289	// ----------------------------------------------------------------
290	getBannerContent() {
291		const bannerDescription =
292			this.config.text?.banner?.description ||
293			`We use cookies on our site to enhance your user experience, provide personalized content, and analyze our traffic.`;
294
295		// Accept button
296		const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || "Accept all";
297		const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel;
298		const acceptAllButton = `<button class="accept-all st-button st-button--primary"${
299			acceptAllButtonLabel && acceptAllButtonLabel !== acceptAllButtonText
300				? ` aria-label="${acceptAllButtonLabel}"`
301				: ""
302		}>${acceptAllButtonText}</button>`;
303
304		// Reject button
305		const rejectNonEssentialButtonText =
306			this.config.text?.banner?.rejectNonEssentialButtonText || "Reject non-essential";
307		const rejectNonEssentialButtonLabel =
308			this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel;
309		const rejectNonEssentialButton = `<button class="reject-all st-button st-button--primary"${
310			rejectNonEssentialButtonLabel &&
311			rejectNonEssentialButtonLabel !== rejectNonEssentialButtonText
312				? ` aria-label="${rejectNonEssentialButtonLabel}"`
313				: ""
314		}>${rejectNonEssentialButtonText}</button>`;
315
316		// Preferences button
317		const preferencesButtonText = this.config.text?.banner?.preferencesButtonText || "Preferences";
318		const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
319		const preferencesButton = `<button class="preferences"${
320			preferencesButtonLabel && preferencesButtonLabel !== preferencesButtonText
321				? ` aria-label="${preferencesButtonLabel}"`
322				: ""
323		}><span>${preferencesButtonText}</span></button>`;
324
325		// Silktide logo link
326		const silktideLogo = `
327      <a class="silktide-logo" href="https://silktide.com/consent-manager" aria-label="Visit the Silktide Consent Manager page">
328        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25" fill="inherit">
329          <path fill-rule="evenodd" clip-rule="evenodd" d="M14.1096 16.7745C13.8895 17.2055 13.3537 17.3805 12.9129 17.1653L8.28443 14.9055L2.73192 17.7651L11.1025 21.9814C11.909 22.3876 12.8725 22.3591 13.6524 21.9058L20.4345 17.9645C21.2845 17.4704 21.7797 16.5522 21.7164 15.5872L21.7088 15.4704C21.6487 14.5561 21.0962 13.7419 20.2579 13.3326L15.6793 11.0972L10.2283 13.9045L13.71 15.6043C14.1507 15.8195 14.3297 16.3434 14.1096 16.7745ZM8.2627 12.9448L13.7136 10.1375L10.2889 8.46543C9.84803 8.25021 9.66911 7.72629 9.88916 7.29524C10.1093 6.86417 10.6451 6.68921 11.0859 6.90442L15.6575 9.13647L21.2171 6.27325L12.8808 2.03496C12.0675 1.62147 11.0928 1.65154 10.3078 2.11432L3.54908 6.09869C2.70732 6.59492 2.21846 7.50845 2.28139 8.46761L2.29003 8.59923C2.35002 9.51362 2.9026 10.3278 3.7409 10.7371L8.2627 12.9448ZM6.31884 13.9458L2.94386 12.2981C1.53727 11.6113 0.610092 10.2451 0.509431 8.71094L0.500795 8.57933C0.3952 6.96993 1.21547 5.4371 2.62787 4.60447L9.38662 0.620092C10.7038 -0.156419 12.3392 -0.206861 13.7039 0.486938L23.3799 5.40639C23.4551 5.44459 23.5224 5.4918 23.5811 5.54596C23.7105 5.62499 23.8209 5.73754 23.897 5.87906C24.1266 6.30534 23.9594 6.83293 23.5234 7.05744L17.6231 10.0961L21.0549 11.7716C22.4615 12.4583 23.3887 13.8245 23.4893 15.3587L23.497 15.4755C23.6033 17.0947 22.7724 18.6354 21.346 19.4644L14.5639 23.4057C13.2554 24.1661 11.6386 24.214 10.2854 23.5324L0.621855 18.6649C0.477299 18.592 0.361696 18.4859 0.279794 18.361C0.210188 18.2968 0.150054 18.2204 0.10296 18.133C-0.126635 17.7067 0.0406445 17.1792 0.47659 16.9546L6.31884 13.9458Z" fill="inherit"/>
330        </svg>
331      </a>
332    `;
333
334		const bannerContent = `
335      ${bannerDescription}
336      <div class="actions">                               
337        ${acceptAllButton}
338        ${rejectNonEssentialButton}
339        <div class="actions-row">
340          ${preferencesButton}
341          ${silktideLogo}
342        </div>
343      </div>
344    `;
345
346		return bannerContent;
347	}
348
349	hasSetInitialCookieChoices() {
350		return !!localStorage.getItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`);
351	}
352
353	createBanner() {
354		// Create banner element
355		this.banner = this.createWrapperChild(this.getBannerContent(), "silktide-banner");
356
357		// Add positioning class from config
358		if (this.banner && this.config.position?.banner) {
359			this.banner.classList.add(this.config.position.banner);
360		}
361
362		// Trigger optional onBannerOpen callback
363		if (this.banner && typeof this.config.onBannerOpen === "function") {
364			this.config.onBannerOpen();
365		}
366	}
367
368	removeBanner() {
369		if (this.banner && this.banner.parentNode) {
370			this.banner.parentNode.removeChild(this.banner);
371			this.banner = null;
372
373			// Trigger optional onBannerClose callback
374			if (typeof this.config.onBannerClose === "function") {
375				this.config.onBannerClose();
376			}
377		}
378	}
379
380	shouldShowBanner() {
381		if (this.config.showBanner === false) {
382			return false;
383		}
384		return (
385			localStorage.getItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`) === null
386		);
387	}
388
389	// ----------------------------------------------------------------
390	// Modal
391	// ----------------------------------------------------------------
392	getModalContent() {
393		const preferencesTitle =
394			this.config.text?.preferences?.title || "Customize your cookie preferences";
395
396		const preferencesDescription =
397			this.config.text?.preferences?.description ||
398			"<p>We respect your right to privacy. You can choose not to allow some types of cookies. Your cookie preferences will apply across our website.</p>";
399
400		// Preferences button
401		const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
402
403		const closeModalButton = `<button class="modal-close"${preferencesButtonLabel ? ` aria-label="${preferencesButtonLabel}"` : ""}>
404      <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
405          <path d="M19.4081 3.41559C20.189 2.6347 20.189 1.36655 19.4081 0.585663C18.6272 -0.195221 17.3591 -0.195221 16.5782 0.585663L10 7.17008L3.41559 0.59191C2.6347 -0.188974 1.36655 -0.188974 0.585663 0.59191C-0.195221 1.37279 -0.195221 2.64095 0.585663 3.42183L7.17008 10L0.59191 16.5844C-0.188974 17.3653 -0.188974 18.6335 0.59191 19.4143C1.37279 20.1952 2.64095 20.1952 3.42183 19.4143L10 12.8299L16.5844 19.4081C17.3653 20.189 18.6335 20.189 19.4143 19.4081C20.1952 18.6272 20.1952 17.3591 19.4143 16.5782L12.8299 10L19.4081 3.41559Z"/>
406      </svg>
407    </button>`;
408
409		const cookieTypes = this.config.cookieTypes || [];
410		const acceptedCookieMap = this.getAcceptedCookies();
411
412		// Accept button
413		const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || "Accept all";
414		const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel;
415		const acceptAllButton = `<button class="preferences-accept-all st-button st-button--primary"${
416			acceptAllButtonLabel && acceptAllButtonLabel !== acceptAllButtonText
417				? ` aria-label="${acceptAllButtonLabel}"`
418				: ""
419		}>${acceptAllButtonText}</button>`;
420
421		// Reject button
422		const rejectNonEssentialButtonText =
423			this.config.text?.banner?.rejectNonEssentialButtonText || "Reject non-essential";
424		const rejectNonEssentialButtonLabel =
425			this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel;
426		const rejectNonEssentialButton = `<button class="preferences-reject-all st-button st-button--primary"${
427			rejectNonEssentialButtonLabel &&
428			rejectNonEssentialButtonLabel !== rejectNonEssentialButtonText
429				? ` aria-label="${rejectNonEssentialButtonLabel}"`
430				: ""
431		}>${rejectNonEssentialButtonText}</button>`;
432
433		// Credit link
434		const creditLinkText =
435			this.config.text?.preferences?.creditLinkText || "Get this banner for free";
436		const creditLinkAccessibleLabel = this.config.text?.preferences?.creditLinkAccessibleLabel;
437		const creditLink = `<a href="https://silktide.com/consent-manager"${
438			creditLinkAccessibleLabel && creditLinkAccessibleLabel !== creditLinkText
439				? ` aria-label="${creditLinkAccessibleLabel}"`
440				: ""
441		}>${creditLinkText}</a>`;
442
443		const modalContent = `
444      <header>
445        <h1>${preferencesTitle}</h1>                    
446        ${closeModalButton}
447      </header>
448      ${preferencesDescription}
449      <section id="cookie-preferences">
450        ${cookieTypes
451					.map((type) => {
452						const accepted = acceptedCookieMap[type.id];
453						let isChecked = false;
454
455						// if it's accepted then show as checked
456						if (accepted) {
457							isChecked = true;
458						}
459
460						// if nothing has been accepted / rejected yet, then show as checked if the default value is true
461						if (!accepted && !this.hasSetInitialCookieChoices()) {
462							isChecked = type.defaultValue;
463						}
464
465						return `
466            <fieldset>
467                <legend>${type.name}</legend>
468                <div class="cookie-type-content">
469                    <div class="cookie-type-description">${type.description}</div>
470                    <label class="switch" for="cookies-${type.id}">
471                        <input type="checkbox" id="cookies-${type.id}" ${
472													type.required ? "checked disabled" : isChecked ? "checked" : ""
473												} />
474                        <span class="switch__pill" aria-hidden="true"></span>
475                        <span class="switch__dot" aria-hidden="true"></span>
476                        <span class="switch__off" aria-hidden="true">Off</span>
477                        <span class="switch__on" aria-hidden="true">On</span>
478                    </label>
479                </div>
480            </fieldset>
481        `;
482					})
483					.join("")}
484      </section>
485      <footer>
486        ${acceptAllButton}
487        ${rejectNonEssentialButton}
488        ${creditLink}
489      </footer>
490    `;
491
492		return modalContent;
493	}
494
495	createModal() {
496		// Create banner element
497		this.modal = this.createWrapperChild(this.getModalContent(), "silktide-modal");
498	}
499
500	toggleModal(show) {
501		if (!this.modal) return;
502
503		this.modal.style.display = show ? "flex" : "none";
504
505		if (show) {
506			this.showBackdrop();
507			this.hideCookieIcon();
508			this.removeBanner();
509			this.preventBodyScroll();
510
511			// Focus the close button
512			const modalCloseButton = this.modal.querySelector(".modal-close");
513			modalCloseButton.focus();
514
515			// Trigger optional onPreferencesOpen callback
516			if (typeof this.config.onPreferencesOpen === "function") {
517				this.config.onPreferencesOpen();
518			}
519
520			this.updateCheckboxState(false); // read from storage when opening
521		} else {
522			// Set that an initial choice was made when closing the modal
523			this.setInitialCookieChoiceMade();
524
525			// Save current checkbox states to storage
526			this.updateCheckboxState(true);
527
528			this.hideBackdrop();
529			this.showCookieIcon();
530			this.allowBodyScroll();
531
532			// Trigger optional onPreferencesClose callback
533			if (typeof this.config.onPreferencesClose === "function") {
534				this.config.onPreferencesClose();
535			}
536		}
537	}
538
539	// ----------------------------------------------------------------
540	// Cookie Icon
541	// ----------------------------------------------------------------
542	getCookieIconContent() {
543		return `
544      <svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
545          <path d="M19.1172 1.15625C19.0547 0.734374 18.7344 0.390624 18.3125 0.328124C16.5859 0.0859365 14.8281 0.398437 13.2813 1.21875L7.5 4.30469C5.96094 5.125 4.71875 6.41406 3.95313 7.98437L1.08594 13.8906C0.320314 15.4609 0.0703136 17.2422 0.375001 18.9609L1.50781 25.4297C1.8125 27.1562 2.64844 28.7344 3.90625 29.9531L8.61719 34.5156C9.875 35.7344 11.4766 36.5156 13.2031 36.7578L19.6875 37.6719C21.4141 37.9141 23.1719 37.6016 24.7188 36.7812L30.5 33.6953C32.0391 32.875 33.2813 31.5859 34.0469 30.0078L36.9141 24.1094C37.6797 22.5391 37.9297 20.7578 37.625 19.0391C37.5547 18.625 37.2109 18.3125 36.7969 18.25C32.7734 17.6094 29.5469 14.5703 28.6328 10.6406C28.4922 10.0469 28.0078 9.59375 27.4063 9.5C23.1406 8.82031 19.7734 5.4375 19.1094 1.15625H19.1172ZM15.25 10.25C15.913 10.25 16.5489 10.5134 17.0178 10.9822C17.4866 11.4511 17.75 12.087 17.75 12.75C17.75 13.413 17.4866 14.0489 17.0178 14.5178C16.5489 14.9866 15.913 15.25 15.25 15.25C14.587 15.25 13.9511 14.9866 13.4822 14.5178C13.0134 14.0489 12.75 13.413 12.75 12.75C12.75 12.087 13.0134 11.4511 13.4822 10.9822C13.9511 10.5134 14.587 10.25 15.25 10.25ZM10.25 25.25C10.25 24.587 10.5134 23.9511 10.9822 23.4822C11.4511 23.0134 12.087 22.75 12.75 22.75C13.413 22.75 14.0489 23.0134 14.5178 23.4822C14.9866 23.9511 15.25 24.587 15.25 25.25C15.25 25.913 14.9866 26.5489 14.5178 27.0178C14.0489 27.4866 13.413 27.75 12.75 27.75C12.087 27.75 11.4511 27.4866 10.9822 27.0178C10.5134 26.5489 10.25 25.913 10.25 25.25ZM27.75 20.25C28.413 20.25 29.0489 20.5134 29.5178 20.9822C29.9866 21.4511 30.25 22.087 30.25 22.75C30.25 23.413 29.9866 24.0489 29.5178 24.5178C29.0489 24.9866 28.413 25.25 27.75 25.25C27.087 25.25 26.4511 24.9866 25.9822 24.5178C25.5134 24.0489 25.25 23.413 25.25 22.75C25.25 22.087 25.5134 21.4511 25.9822 20.9822C26.4511 20.5134 27.087 20.25 27.75 20.25Z" />
546      </svg>
547    `;
548	}
549
550	createCookieIcon() {
551		this.cookieIcon = document.createElement("button");
552		this.cookieIcon.id = "silktide-cookie-icon";
553		this.cookieIcon.innerHTML = this.getCookieIconContent();
554
555		if (this.config.text?.banner?.preferencesButtonAccessibleLabel) {
556			this.cookieIcon.ariaLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
557		}
558
559		// Ensure wrapper exists
560		if (!this.wrapper || !document.body.contains(this.wrapper)) {
561			this.createWrapper();
562		}
563
564		// Append child to wrapper
565		this.wrapper.appendChild(this.cookieIcon);
566
567		// Add positioning class from config
568		if (this.cookieIcon && this.config.cookieIcon?.position) {
569			this.cookieIcon.classList.add(this.config.cookieIcon.position);
570		}
571
572		// Add color scheme class from config
573		if (this.cookieIcon && this.config.cookieIcon?.colorScheme) {
574			this.cookieIcon.classList.add(this.config.cookieIcon.colorScheme);
575		}
576	}
577
578	showCookieIcon() {
579		if (this.cookieIcon) {
580			this.cookieIcon.style.display = "flex";
581		}
582	}
583
584	hideCookieIcon() {
585		if (this.cookieIcon) {
586			this.cookieIcon.style.display = "none";
587		}
588	}
589
590	/**
591	 * This runs if the user closes the modal without making a choice for the first time
592	 * We apply the default values and the necessary values as default
593	 */
594	handleClosedWithNoChoice() {
595		this.config.cookieTypes.forEach((type) => {
596			let accepted = true;
597			// Set localStorage and run accept/reject callbacks
598			if (type.required == true) {
599				localStorage.setItem(
600					`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
601					accepted.toString()
602				);
603			} else if (type.defaultValue) {
604				localStorage.setItem(
605					`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
606					accepted.toString()
607				);
608			} else {
609				accepted = false;
610				localStorage.setItem(
611					`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
612					accepted.toString()
613				);
614			}
615
616			if (accepted) {
617				if (typeof type.onAccept === "function") {
618					type.onAccept();
619				}
620			} else {
621				if (typeof type.onReject === "function") {
622					type.onReject();
623				}
624			}
625			// set the flag to say that the cookie choice has been made
626			this.setInitialCookieChoiceMade();
627			this.updateCheckboxState();
628		});
629	}
630
631	// ----------------------------------------------------------------
632	// Focusable Elements
633	// ----------------------------------------------------------------
634	getFocusableElements(element) {
635		return element.querySelectorAll(
636			'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
637		);
638	}
639
640	// ----------------------------------------------------------------
641	// Event Listeners
642	// ----------------------------------------------------------------
643	setupEventListeners() {
644		// Check Banner exists before trying to add event listeners
645		if (this.banner) {
646			// Get the buttons
647			const acceptButton = this.banner.querySelector(".accept-all");
648			const rejectButton = this.banner.querySelector(".reject-all");
649			const preferencesButton = this.banner.querySelector(".preferences");
650
651			// Add event listeners to the buttons
652			acceptButton?.addEventListener("click", () => this.handleCookieChoice(true));
653			rejectButton?.addEventListener("click", () => this.handleCookieChoice(false));
654			preferencesButton?.addEventListener("click", () => {
655				this.showBackdrop();
656				this.toggleModal(true);
657			});
658
659			// Focus Trap
660			const focusableElements = this.getFocusableElements(this.banner);
661			const firstFocusableEl = focusableElements[0];
662			const lastFocusableEl = focusableElements[focusableElements.length - 1];
663
664			// Add keydown event listener to handle tab navigation
665			this.banner.addEventListener("keydown", (e) => {
666				if (e.key === "Tab") {
667					if (e.shiftKey) {
668						if (document.activeElement === firstFocusableEl) {
669							lastFocusableEl.focus();
670							e.preventDefault();
671						}
672					} else {
673						if (document.activeElement === lastFocusableEl) {
674							firstFocusableEl.focus();
675							e.preventDefault();
676						}
677					}
678				}
679			});
680
681			// Set initial focus
682			if (this.config.mode !== "wizard") {
683				acceptButton?.focus();
684			}
685		}
686
687		// Check Modal exists before trying to add event listeners
688		if (this.modal) {
689			const closeButton = this.modal.querySelector(".modal-close");
690			const acceptAllButton = this.modal.querySelector(".preferences-accept-all");
691			const rejectAllButton = this.modal.querySelector(".preferences-reject-all");
692
693			closeButton?.addEventListener("click", () => {
694				this.toggleModal(false);
695
696				const hasMadeFirstChoice = this.hasSetInitialCookieChoices();
697
698				if (hasMadeFirstChoice) {
699					// run through the callbacks based on the current localStorage state
700					this.runStoredCookiePreferenceCallbacks();
701				} else {
702					// handle the case where the user closes without making a choice for the first time
703					this.handleClosedWithNoChoice();
704				}
705			});
706			acceptAllButton?.addEventListener("click", () => this.handleCookieChoice(true));
707			rejectAllButton?.addEventListener("click", () => this.handleCookieChoice(false));
708
709			// Banner Focus Trap
710			const focusableElements = this.getFocusableElements(this.modal);
711			const firstFocusableEl = focusableElements[0];
712			const lastFocusableEl = focusableElements[focusableElements.length - 1];
713
714			this.modal.addEventListener("keydown", (e) => {
715				if (e.key === "Tab") {
716					if (e.shiftKey) {
717						if (document.activeElement === firstFocusableEl) {
718							lastFocusableEl.focus();
719							e.preventDefault();
720						}
721					} else {
722						if (document.activeElement === lastFocusableEl) {
723							firstFocusableEl.focus();
724							e.preventDefault();
725						}
726					}
727				}
728				if (e.key === "Escape") {
729					this.toggleModal(false);
730				}
731			});
732
733			closeButton?.focus();
734
735			// Update the checkbox event listeners
736			const preferencesSection = this.modal.querySelector("#cookie-preferences");
737			const checkboxes = preferencesSection.querySelectorAll('input[type="checkbox"]');
738
739			checkboxes.forEach((checkbox) => {
740				checkbox.addEventListener("change", (event) => {
741					const [, cookieId] = event.target.id.split("cookies-");
742					const isAccepted = event.target.checked;
743					const previousValue =
744						localStorage.getItem(`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`) ===
745						"true";
746
747					// Only proceed if the value has actually changed
748					if (isAccepted !== previousValue) {
749						// Find the corresponding cookie type
750						const cookieType = this.config.cookieTypes.find((type) => type.id === cookieId);
751
752						if (cookieType) {
753							// Update localStorage
754							localStorage.setItem(
755								`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`,
756								isAccepted.toString()
757							);
758
759							// Run the appropriate callback only if the value changed
760							if (isAccepted && typeof cookieType.onAccept === "function") {
761								cookieType.onAccept();
762							} else if (!isAccepted && typeof cookieType.onReject === "function") {
763								cookieType.onReject();
764							}
765						}
766					}
767				});
768			});
769		}
770
771		// Check Cookie Icon exists before trying to add event listeners
772		if (this.cookieIcon) {
773			this.cookieIcon.addEventListener("click", () => {
774				// If modal is not found, create it
775				if (!this.modal) {
776					this.createModal();
777					this.toggleModal(true);
778					this.hideCookieIcon();
779				}
780				// If modal is hidden, show it
781				else if (this.modal.style.display === "none" || this.modal.style.display === "") {
782					this.toggleModal(true);
783					this.hideCookieIcon();
784				}
785				// If modal is visible, hide it
786				else {
787					this.toggleModal(false);
788				}
789			});
790		}
791	}
792
793	getBannerSuffix() {
794		if (this.config.bannerSuffix) {
795			return "_" + this.config.bannerSuffix;
796		}
797		return "";
798	}
799
800	preventBodyScroll() {
801		document.body.style.overflow = "hidden";
802		// Prevent iOS Safari scrolling
803		document.body.style.position = "fixed";
804		document.body.style.width = "100%";
805	}
806
807	allowBodyScroll() {
808		document.body.style.overflow = "";
809		document.body.style.position = "";
810		document.body.style.width = "";
811	}
812}
813
814(function () {
815	window.silktideCookieBannerManager = {};
816
817	let config = {};
818	let cookieBanner;
819
820	function updateCookieBannerConfig(userConfig = {}) {
821		config = { ...config, ...userConfig };
822
823		// If cookie banner exists, destroy and recreate it with new config
824		if (cookieBanner) {
825			cookieBanner.destroyCookieBanner(); // We'll need to add this method
826			cookieBanner = null;
827		}
828
829		// Only initialize if document.body exists
830		if (document.body) {
831			initCookieBanner();
832		} else {
833			// Wait for DOM to be ready
834			document.addEventListener("DOMContentLoaded", initCookieBanner, {
835				once: true,
836			});
837		}
838	}
839
840	function initCookieBanner() {
841		if (!cookieBanner) {
842			cookieBanner = new SilktideCookieBanner(config); // Pass config to the CookieBanner instance
843		}
844	}
845
846	function injectScript(url, loadOption) {
847		// Check if script with this URL already exists
848		const existingScript = document.querySelector(`script[src="${url}"]`);
849		if (existingScript) {
850			return; // Script already exists, don't add it again
851		}
852
853		const script = document.createElement("script");
854		script.src = url;
855
856		// Apply the async or defer attribute based on the loadOption parameter
857		if (loadOption === "async") {
858			script.async = true;
859		} else if (loadOption === "defer") {
860			script.defer = true;
861		}
862
863		document.head.appendChild(script);
864	}
865
866	window.silktideCookieBannerManager.initCookieBanner = initCookieBanner;
867	window.silktideCookieBannerManager.updateCookieBannerConfig = updateCookieBannerConfig;
868	window.silktideCookieBannerManager.injectScript = injectScript;
869
870	if (document.readyState === "loading") {
871		document.addEventListener("DOMContentLoaded", initCookieBanner, {
872			once: true,
873		});
874	} else {
875		initCookieBanner();
876	}
877})();

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.