PageSourceSearch

https://detectiveconanworld.com/js/consent.js

js detectiveconanworld.com collected 2026-10-02 07:18:08 UTC 34,340 bytes, 895 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(
131            `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`,
132            'true'
133          );
134        } else {
135          localStorage.setItem(
136            `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`,
137            currentState.toString()
138          );
139          
140          // Run appropriate callback
141          if (currentState && typeof cookieType.onAccept === 'function') {
142            cookieType.onAccept();
143          } else if (!currentState && typeof cookieType.onReject === 'function') {
144            cookieType.onReject();
145          }
146        }
147      } else {
148        // When reading values (opening modal)
149        if (cookieType.required) {
150          checkbox.checked = true;
151          checkbox.disabled = true;
152        } else {
153          const storedValue = localStorage.getItem(
154            `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`
155          );
156          
157          if (storedValue !== null) {
158            checkbox.checked = storedValue === 'true';
159          } else {
160            checkbox.checked = !!cookieType.defaultValue;
161          }
162        }
163      }
164    });
165  }
166
167  setInitialCookieChoiceMade() {
168    window.localStorage.setItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`, 1);
169  }
170
171  // ----------------------------------------------------------------
172  // Consent Handling
173  // ----------------------------------------------------------------
174  handleCookieChoice(accepted) {
175    // We set that an initial choice was made regardless of what it was so we don't show the banner again
176    this.setInitialCookieChoiceMade();
177
178    this.removeBanner();
179    this.hideBackdrop();
180    this.toggleModal(false);
181    this.showCookieIcon();
182
183    this.config.cookieTypes.forEach((type) => {
184      // Set localStorage and run accept/reject callbacks
185      if (type.required == true) {
186        localStorage.setItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`, 'true');
187        if (typeof type.onAccept === 'function') { type.onAccept() }
188      } else {
189        localStorage.setItem(
190          `silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
191          accepted.toString(),
192        );
193
194        if (accepted) {
195          if (typeof type.onAccept === 'function') { type.onAccept(); }
196        } else {
197          if (typeof type.onReject === 'function') { type.onReject(); }
198        }
199      }
200    });
201
202    // Trigger optional onAcceptAll/onRejectAll callbacks
203    if (accepted && typeof this.config.onAcceptAll === 'function') {
204      if (typeof this.config.onAcceptAll === 'function') { this.config.onAcceptAll(); }
205    } else if (typeof this.config.onRejectAll === 'function') {
206      if (typeof this.config.onRejectAll === 'function') { this.config.onRejectAll(); }
207    }
208
209    // finally update the checkboxes in the modal with the values from localStorage
210    this.updateCheckboxState();
211  }
212
213  getAcceptedCookies() {
214    return (this.config.cookieTypes || []).reduce((acc, cookieType) => {
215      acc[cookieType.id] =
216        localStorage.getItem(`silktideCookieChoice_${cookieType.id}${this.getBannerSuffix()}`) ===
217        'true';
218      return acc;
219    }, {});
220  }
221
222  runAcceptedCookieCallbacks() {
223    if (!this.config.cookieTypes) return;
224
225    const acceptedCookies = this.getAcceptedCookies();
226    this.config.cookieTypes.forEach((type) => {
227      if (type.required) return; // we run required cookies separately in loadRequiredCookies
228      if (acceptedCookies[type.id] && typeof type.onAccept === 'function') {
229        if (typeof type.onAccept === 'function') { type.onAccept(); }
230      }
231    });
232  }
233
234  runRejectedCookieCallbacks() {
235    if (!this.config.cookieTypes) return;
236
237    const rejectedCookies = this.getRejectedCookies();
238    this.config.cookieTypes.forEach((type) => {
239      if (rejectedCookies[type.id] && typeof type.onReject === 'function') {
240        if (typeof type.onReject === 'function') { type.onReject(); }
241      }
242    });
243  }
244
245  /**
246   * Run through all of the cookie callbacks based on the current localStorage values
247   */
248  runStoredCookiePreferenceCallbacks() {
249    // BEGIN Addition by Maurice Lebon [2026-04-24]
250    // if (!this.config.cookieTypes) return;
251    // END Addition
252
253    this.config.cookieTypes.forEach((type) => {
254      const accepted =
255        localStorage.getItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`) === 'true';
256      // Set localStorage and run accept/reject callbacks
257      if (accepted) {
258        if (typeof type.onAccept === 'function') { type.onAccept(); }
259      } else {
260        if (typeof type.onReject === 'function') { type.onReject(); }
261      }
262    });
263  }
264
265  loadRequiredCookies() {
266    if (!this.config.cookieTypes) return;
267    this.config.cookieTypes.forEach((cookie) => {
268      if (cookie.required && typeof cookie.onAccept === 'function') {
269        if (typeof cookie.onAccept === 'function') { cookie.onAccept(); }
270      }
271    });
272  }
273
274  // ----------------------------------------------------------------
275  // Banner
276  // ----------------------------------------------------------------
277  getBannerContent() {
278    const bannerDescription =
279      this.config.text?.banner?.description ||
280      "<p>We use cookies on our site to enhance your user experience, provide personalized content, and analyze our traffic.</p>";
281
282    // Accept button
283    const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || 'Accept all';
284    const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel;
285    const acceptAllButton = `<button class="accept-all st-button st-button--primary"${
286      acceptAllButtonLabel && acceptAllButtonLabel !== acceptAllButtonText 
287        ? ` aria-label="${acceptAllButtonLabel}"` 
288        : ''
289    }>${acceptAllButtonText}</button>`;
290    
291    // Reject button
292    const rejectNonEssentialButtonText = this.config.text?.banner?.rejectNonEssentialButtonText || 'Reject non-essential';
293    const rejectNonEssentialButtonLabel = this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel;
294    const rejectNonEssentialButton = `<button class="reject-all st-button st-button--primary"${
295      rejectNonEssentialButtonLabel && rejectNonEssentialButtonLabel !== rejectNonEssentialButtonText 
296        ? ` aria-label="${rejectNonEssentialButtonLabel}"` 
297        : ''
298    }>${rejectNonEssentialButtonText}</button>`;
299
300    // Preferences button
301    const preferencesButtonText = this.config.text?.banner?.preferencesButtonText || 'Preferences';
302    const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
303    const preferencesButton = `<button class="preferences"${
304      preferencesButtonLabel && preferencesButtonLabel !== preferencesButtonText 
305        ? ` aria-label="${preferencesButtonLabel}"` 
306        : ''
307    }><span>${preferencesButtonText}</span></button>`;
308    
309
310    // Silktide logo link
311    // BEGIN Modified by Maurice Lebon [2026-04-24]
312    const silktideLogo = ''; /* `
313      <a class="silktide-logo" href="https://silktide.com/consent-manager" target="_blank" rel="noreferrer" aria-label="Visit the Silktide Consent Manager page">
314        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25" fill="inherit">
315          <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"/>
316        </svg>
317      </a>
318    `; */
319    // END Modified
320
321    const bannerContent = `
322      ${bannerDescription}
323      <div class="actions">                               
324        ${acceptAllButton}
325        ${rejectNonEssentialButton}
326        <div class="actions-row">
327          ${preferencesButton}
328          ${silktideLogo}
329        </div>
330      </div>
331    `;
332
333    return bannerContent;
334  }
335
336  hasSetInitialCookieChoices() {
337    return !!localStorage.getItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`);
338  }
339
340  createBanner() {
341    // Create banner element
342    this.banner = this.createWrapperChild(this.getBannerContent(), 'silktide-banner');
343
344    // Add positioning class from config
345    if (this.banner && this.config.position?.banner) {
346      this.banner.classList.add(this.config.position.banner);
347    }
348
349    // Trigger optional onBannerOpen callback
350    if (this.banner && typeof this.config.onBannerOpen === 'function') {
351      this.config.onBannerOpen();
352    }
353  }
354
355  removeBanner() {
356    if (this.banner && this.banner.parentNode) {
357      this.banner.parentNode.removeChild(this.banner);
358      this.banner = null;
359
360      // Trigger optional onBannerClose callback
361      if (typeof this.config.onBannerClose === 'function') {
362        this.config.onBannerClose();
363      }
364    }
365  }
366
367  shouldShowBanner() {
368    if (this.config.showBanner === false) {
369      return false;
370    }
371    return (
372      localStorage.getItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`) === null
373    );
374  }
375
376  // ----------------------------------------------------------------
377  // Modal
378  // ----------------------------------------------------------------
379  getModalContent() {
380    const preferencesTitle =
381      this.config.text?.preferences?.title || 'Customize your cookie preferences';
382    
383    const preferencesDescription =
384      this.config.text?.preferences?.description ||
385      "<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>";
386    
387    // Preferences button
388    const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
389
390    const closeModalButton = `<button class="modal-close"${preferencesButtonLabel ? ` aria-label="${preferencesButtonLabel}"` : ''}>
391      <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
392          <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"/>
393      </svg>
394    </button>`;
395    
396
397    const cookieTypes = this.config.cookieTypes || [];
398    const acceptedCookieMap = this.getAcceptedCookies();
399
400    // Accept button
401    const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || 'Accept all';
402    const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel;
403    const acceptAllButton = `<button class="preferences-accept-all st-button st-button--primary"${
404      acceptAllButtonLabel && acceptAllButtonLabel !== acceptAllButtonText 
405        ? ` aria-label="${acceptAllButtonLabel}"` 
406        : ''
407    }>${acceptAllButtonText}</button>`;
408    
409    // Reject button
410    const rejectNonEssentialButtonText = this.config.text?.banner?.rejectNonEssentialButtonText || 'Reject non-essential';
411    const rejectNonEssentialButtonLabel = this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel;
412    const rejectNonEssentialButton = `<button class="preferences-reject-all st-button st-button--primary"${
413      rejectNonEssentialButtonLabel && rejectNonEssentialButtonLabel !== rejectNonEssentialButtonText 
414        ? ` aria-label="${rejectNonEssentialButtonLabel}"` 
415        : ''
416    }>${rejectNonEssentialButtonText}</button>`;
417    
418    // Credit link
419    const creditLinkText = this.config.text?.preferences?.creditLinkText || 'Get this banner for free';
420    const creditLinkAccessibleLabel = this.config.text?.preferences?.creditLinkAccessibleLabel;
421    const creditLink = `<a href="https://silktide.com/consent-manager" target="_blank" rel="noreferrer"${
422      creditLinkAccessibleLabel && creditLinkAccessibleLabel !== creditLinkText
423        ? ` aria-label="${creditLinkAccessibleLabel}"`
424        : ''
425    }>${creditLinkText}</a>`;
426    
427    
428
429    const modalContent = `
430      <header>
431        <h1>${preferencesTitle}</h1>                    
432        ${closeModalButton}
433      </header>
434      ${preferencesDescription}
435      <section id="cookie-preferences">
436        ${cookieTypes
437          .map((type) => {
438            const accepted = acceptedCookieMap[type.id];
439            let isChecked = false;
440
441            // if it's accepted then show as checked
442            if (accepted) {
443              isChecked = true;
444            }
445
446            // if nothing has been accepted / rejected yet, then show as checked if the default value is true
447            if (!accepted && !this.hasSetInitialCookieChoices()) {
448              isChecked = type.defaultValue;
449            }
450
451            return `
452            <fieldset>
453                <legend>${type.name}</legend>
454                <div class="cookie-type-content">
455                    <div class="cookie-type-description">${type.description}</div>
456                    <label class="switch" for="cookies-${type.id}">
457                        <input type="checkbox" id="cookies-${type.id}" ${
458              type.required ? 'checked disabled' : isChecked ? 'checked' : ''
459            } />
460                        <span class="switch__pill" aria-hidden="true"></span>
461                        <span class="switch__dot" aria-hidden="true"></span>
462                        <span class="switch__off" aria-hidden="true">Off</span>
463                        <span class="switch__on" aria-hidden="true">On</span>
464                    </label>
465                </div>
466            </fieldset>
467        `;
468          })
469          .join('')}
470      </section>
471      <footer>
472        ${acceptAllButton}
473        ${rejectNonEssentialButton}
474        ${creditLink}
475      </footer>
476    `;
477
478    return modalContent;
479  }
480
481  createModal() {
482    // Create banner element
483    this.modal = this.createWrapperChild(this.getModalContent(), 'silktide-modal');
484  }
485
486  toggleModal(show) {
487    if (!this.modal) return;
488
489    this.modal.style.display = show ? 'flex' : 'none';
490
491    if (show) {
492      this.showBackdrop();
493      this.hideCookieIcon();
494      this.removeBanner();
495      this.preventBodyScroll();
496
497      // Focus the close button
498      const modalCloseButton = this.modal.querySelector('.modal-close');
499      modalCloseButton.focus();
500
501      // Trigger optional onPreferencesOpen callback
502      if (typeof this.config.onPreferencesOpen === 'function') {
503        this.config.onPreferencesOpen();
504      }
505
506      this.updateCheckboxState(false); // read from storage when opening
507    } else {
508      // Set that an initial choice was made when closing the modal
509      this.setInitialCookieChoiceMade();
510      
511      // Save current checkbox states to storage
512      this.updateCheckboxState(true);
513
514      this.hideBackdrop();
515      this.showCookieIcon();
516      this.allowBodyScroll();
517
518      // Trigger optional onPreferencesClose callback
519      if (typeof this.config.onPreferencesClose === 'function') {
520        this.config.onPreferencesClose();
521      }
522    }
523  }
524
525  // ----------------------------------------------------------------
526  // Cookie Icon
527  // ----------------------------------------------------------------
528  getCookieIconContent() {
529    return `
530      <svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
531          <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" />
532      </svg>
533    `;
534  }
535
536  createCookieIcon() {
537    this.cookieIcon = document.createElement('button');
538    this.cookieIcon.id = 'silktide-cookie-icon';
539    this.cookieIcon.title = 'Manage your cookie preferences for this site';
540    this.cookieIcon.innerHTML = this.getCookieIconContent();
541
542    if (this.config.text?.banner?.preferencesButtonAccessibleLabel) {
543      this.cookieIcon.ariaLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel;
544    }
545
546    // Ensure wrapper exists
547    if (!this.wrapper || !document.body.contains(this.wrapper)) {
548      this.createWrapper();
549    }
550
551    // Append child to wrapper
552    this.wrapper.appendChild(this.cookieIcon);
553
554    // Add positioning class from config
555    if (this.cookieIcon && this.config.cookieIcon?.position) {
556      this.cookieIcon.classList.add(this.config.cookieIcon.position);
557    }
558
559    // Add color scheme class from config
560    if (this.cookieIcon && this.config.cookieIcon?.colorScheme) {
561      this.cookieIcon.classList.add(this.config.cookieIcon.colorScheme);
562    }
563  }
564
565  showCookieIcon() {
566    if (this.cookieIcon) {
567      this.cookieIcon.style.display = 'flex';
568    }
569  }
570
571  hideCookieIcon() {
572    if (this.cookieIcon) {
573      this.cookieIcon.style.display = 'none';
574    }
575  }
576
577  /**
578   * This runs if the user closes the modal without making a choice for the first time
579   * We apply the default values and the necessary values as default
580   */
581  handleClosedWithNoChoice() {
582    this.config.cookieTypes.forEach((type) => {
583      let accepted = true;
584      // Set localStorage and run accept/reject callbacks
585      if (type.required == true || type.defaultValue) {
586        localStorage.setItem(
587          `silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
588          accepted.toString(),
589        );
590      } else {
591        accepted = false;
592        localStorage.setItem(
593          `silktideCookieChoice_${type.id}${this.getBannerSuffix()}`,
594          accepted.toString(),
595        );
596      }
597
598      if (accepted) {
599        if (typeof type.onAccept === 'function') { type.onAccept(); }
600      } else {
601        if (typeof type.onReject === 'function') { type.onReject(); }
602      }
603      // set the flag to say that the cookie choice has been made
604      this.setInitialCookieChoiceMade();
605      this.updateCheckboxState();
606    });
607  }
608
609  // ----------------------------------------------------------------
610  // Focusable Elements
611  // ----------------------------------------------------------------
612  getFocusableElements(element) {
613    return element.querySelectorAll(
614      'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
615    );
616  }
617
618  // ----------------------------------------------------------------
619  // Event Listeners
620  // ----------------------------------------------------------------
621  setupEventListeners() {
622    // Check Banner exists before trying to add event listeners
623    if (this.banner) {
624      // Get the buttons
625      const acceptButton = this.banner.querySelector('.accept-all');
626      const rejectButton = this.banner.querySelector('.reject-all');
627      const preferencesButton = this.banner.querySelector('.preferences');
628
629      // Add event listeners to the buttons
630      acceptButton?.addEventListener('click', () => this.handleCookieChoice(true));
631      rejectButton?.addEventListener('click', () => this.handleCookieChoice(false));
632      preferencesButton?.addEventListener('click', () => {
633        this.showBackdrop();
634        this.toggleModal(true);
635      });
636
637      // Focus Trap
638      const focusableElements = this.getFocusableElements(this.banner);
639      const firstFocusableEl = focusableElements[0];
640      const lastFocusableEl = focusableElements[focusableElements.length - 1];
641
642      // Add keydown event listener to handle tab navigation
643      this.banner.addEventListener('keydown', (e) => {
644        if (e.key === 'Tab') {
645          if (e.shiftKey) {
646            if (document.activeElement === firstFocusableEl) {
647              lastFocusableEl.focus();
648              e.preventDefault();
649            }
650          } else {
651            if (document.activeElement === lastFocusableEl) {
652              firstFocusableEl.focus();
653              e.preventDefault();
654            }
655          }
656        }
657      });
658
659      // Set initial focus
660      if (this.config.mode !== 'wizard') {
661        acceptButton?.focus();
662      }
663    }
664
665    // Check Modal exists before trying to add event listeners
666    if (this.modal) {
667      const closeButton = this.modal.querySelector('.modal-close');
668      const acceptAllButton = this.modal.querySelector('.preferences-accept-all');
669      const rejectAllButton = this.modal.querySelector('.preferences-reject-all');
670
671      closeButton?.addEventListener('click', () => {
672        this.toggleModal(false);
673
674        const hasMadeFirstChoice = this.hasSetInitialCookieChoices();
675
676        if (hasMadeFirstChoice) {
677          // run through the callbacks based on the current localStorage state
678          this.runStoredCookiePreferenceCallbacks();
679        } else {
680          // handle the case where the user closes without making a choice for the first time
681          this.handleClosedWithNoChoice();
682        }
683      });
684      acceptAllButton?.addEventListener('click', () => this.handleCookieChoice(true));
685      rejectAllButton?.addEventListener('click', () => this.handleCookieChoice(false));
686
687      // Banner Focus Trap
688      const focusableElements = this.getFocusableElements(this.modal);
689      const firstFocusableEl = focusableElements[0];
690      const lastFocusableEl = focusableElements[focusableElements.length - 1];
691
692      this.modal.addEventListener('keydown', (e) => {
693        if (e.key === 'Tab') {
694          if (e.shiftKey) {
695            if (document.activeElement === firstFocusableEl) {
696              lastFocusableEl.focus();
697              e.preventDefault();
698            }
699          } else {
700            if (document.activeElement === lastFocusableEl) {
701              firstFocusableEl.focus();
702              e.preventDefault();
703            }
704          }
705        }
706        if (e.key === 'Escape') {
707          this.toggleModal(false);
708        }
709      });
710
711      closeButton?.focus();
712
713      // Update the checkbox event listeners
714      const preferencesSection = this.modal.querySelector('#cookie-preferences');
715      const checkboxes = preferencesSection.querySelectorAll('input[type="checkbox"]');
716      
717      checkboxes.forEach(checkbox => {
718        checkbox.addEventListener('change', (event) => {
719          const [, cookieId] = event.target.id.split('cookies-');
720          const isAccepted = event.target.checked;
721          const previousValue = localStorage.getItem(
722            `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`
723          ) === 'true';
724          
725          // Only proceed if the value has actually changed
726          if (isAccepted !== previousValue) {
727            // Find the corresponding cookie type
728            const cookieType = this.config.cookieTypes.find(type => type.id === cookieId);
729            
730            if (cookieType) {
731              // Update localStorage
732              localStorage.setItem(
733                `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`,
734                isAccepted.toString()
735              );
736              
737              // Run the appropriate callback only if the value changed
738              if (isAccepted && typeof cookieType.onAccept === 'function') {
739                cookieType.onAccept();
740              } else if (!isAccepted && typeof cookieType.onReject === 'function') {
741                cookieType.onReject();
742              }
743            }
744          }
745        });
746      });
747    }
748
749    // BEGIN Modified by Maurice Lebon [2026-04-24]
750    // Check Cookie Icon exists before trying to add event listeners
751    // if (this.cookieIcon) {
752
753      /* this.cookieIcon.addEventListener('click', () => {
754        // If modal is not found, create it
755        if (!this.modal) {
756          this.createModal();
757          this.toggleModal(true);
758          this.hideCookieIcon();
759        }
760        // If modal is hidden, show it
761        else if (this.modal.style.display === 'none' || this.modal.style.display === '') {
762          this.toggleModal(true);
763          this.hideCookieIcon();
764        }
765        // If modal is visible, hide it
766        else {
767          this.toggleModal(false);
768        }
769      }); */
770
771      // Addition by ML
772      const cookieLink = document.querySelectorAll('.dcw-cookie-link');
773      if (cookieLink) {
774        const cookieLinkArray = [...cookieLink];
775        cookieLinkArray.forEach(link => {
776          link.addEventListener('click', () => {
777            // If modal is not found, create it
778            if (!this.modal) {
779              this.createModal();
780              this.toggleModal(true);
781            }
782            // If modal is hidden, show it
783            else if (this.modal.style.display === 'none' || this.modal.style.display === '') {
784              this.toggleModal(true);
785            }
786            // If modal is visible, hide it
787            else {
788              this.toggleModal(false);
789            }
790          });
791        })
792      }
793      /* if (cookieLink) {
794
795        cookieLink.addEventListener('click', () => {
796          // If modal is not found, create it
797          if (!this.modal) {
798            this.createModal();
799            this.toggleModal(true);
800          }
801          // If modal is hidden, show it
802          else if (this.modal.style.display === 'none' || this.modal.style.display === '') {
803            this.toggleModal(true);
804          }
805          // If modal is visible, hide it
806          else {
807            this.toggleModal(false);
808          }
809        });
810      } */
811    //}
812    // END Modified
813  }
814
815  getBannerSuffix() {
816    if (this.config.bannerSuffix) {
817      return '_' + this.config.bannerSuffix;
818    }
819    return '';
820  }
821
822  preventBodyScroll() {
823    document.body.style.overflow = 'hidden';
824    // Prevent iOS Safari scrolling
825    document.body.style.position = 'fixed';
826    document.body.style.width = '100%';
827  }
828
829  allowBodyScroll() {
830    document.body.style.overflow = '';
831    document.body.style.position = '';
832    document.body.style.width = '';
833  }
834}
835
836(function () {
837  window.silktideCookieBannerManager = {};
838
839  let config = {};
840  let cookieBanner;
841
842  function updateCookieBannerConfig(userConfig = {}) {
843    config = {...config, ...userConfig};
844
845    // If cookie banner exists, destroy and recreate it with new config
846    if (cookieBanner) {
847      cookieBanner.destroyCookieBanner(); // We'll need to add this method
848      cookieBanner = null;
849    }
850
851    // Only initialize if document.body exists
852    if (document.body) {
853      initCookieBanner();
854    } else {
855      // Wait for DOM to be ready
856      document.addEventListener('DOMContentLoaded', initCookieBanner, {once: true});
857    }
858  }
859
860  function initCookieBanner() {
861    if (!cookieBanner) {
862      cookieBanner = new SilktideCookieBanner(config); // Pass config to the CookieBanner instance
863    }
864  }
865
866  function injectScript(url, loadOption) {
867    // Check if script with this URL already exists
868    const existingScript = document.querySelector(`script[src="${url}"]`);
869    if (existingScript) {
870      return; // Script already exists, don't add it again
871    }
872
873    const script = document.createElement('script');
874    script.src = url;
875
876    // Apply the async or defer attribute based on the loadOption parameter
877    if (loadOption === 'async') {
878      script.async = true;
879    } else if (loadOption === 'defer') {
880      script.defer = true;
881    }
882
883    document.head.appendChild(script);
884  }
885
886  window.silktideCookieBannerManager.initCookieBanner = initCookieBanner;
887  window.silktideCookieBannerManager.updateCookieBannerConfig = updateCookieBannerConfig;
888  window.silktideCookieBannerManager.injectScript = injectScript;
889
890  if (document.readyState === 'loading') {
891    document.addEventListener('DOMContentLoaded', initCookieBanner, {once: true});
892  } else {
893    initCookieBanner();
894  }
895})();

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.