PageSourceSearch

https://holofin.ai/static/vendors/silktide-consent-manager.d17cd9a63b57.js

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

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.