// Silktide Consent Manager - https://silktide.com/consent-manager/ class SilktideCookieBanner { constructor(config) { this.config = config; this.wrapper = null; this.banner = null; this.modal = null; this.cookieIcon = null; this.backdrop = null; this.createWrapper(); if (this.shouldShowBackdrop()) { this.createBackdrop(); } this.createCookieIcon(); this.createModal(); if (this.shouldShowBanner()) { this.createBanner(); this.showBackdrop(); // while banner is visible -> icon must stay hidden this.hideCookieIcon(); } else { // ✅ NEW: icon follows the same "once per day" rule as the banner if (this.shouldShowCookieIcon()) { this.showCookieIcon(); } else { this.hideCookieIcon(); } } this.setupEventListeners(); if (this.hasSetInitialCookieChoices()) { this.loadRequiredCookies(); this.runAcceptedCookieCallbacks(); } } destroyCookieBanner() { if (this.wrapper && this.wrapper.parentNode) { this.wrapper.parentNode.removeChild(this.wrapper); } this.allowBodyScroll(); this.wrapper = null; this.banner = null; this.modal = null; this.cookieIcon = null; this.backdrop = null; } // ---------------------------------------------------------------- // Daily cooldown // ---------------------------------------------------------------- getCooldownKey() { return `silktideCookieBanner_CooldownUntil${this.getBannerSuffix()}`; } setCooldownOneDay() { const until = Date.now() + 24 * 60 * 60 * 1000; localStorage.setItem(this.getCooldownKey(), String(until)); } isInCooldown() { const raw = localStorage.getItem(this.getCooldownKey()); const until = raw ? parseInt(raw, 10) : 0; return Number.isFinite(until) && until > Date.now(); } // ✅ NEW: icon should be shown only when banner is allowed to show (same daily logic) shouldShowCookieIcon() { if (this.config.showCookieIcon === false) return false; // First visit: banner shows -> icon stays hidden if (!this.hasSetInitialCookieChoices()) return false; // If in cooldown -> hide icon as well if (this.isInCooldown()) return false; // Otherwise (next day) -> allow icon return true; } // ---------------------------------------------------------------- // Wrapper // ---------------------------------------------------------------- createWrapper() { this.wrapper = document.createElement('div'); this.wrapper.id = 'silktide-wrapper'; document.body.insertBefore(this.wrapper, document.body.firstChild); } // ---------------------------------------------------------------- // Wrapper Child Generator // ---------------------------------------------------------------- createWrapperChild(htmlContent, id) { const child = document.createElement('div'); child.id = id; if (htmlContent != null) child.innerHTML = htmlContent; if (!this.wrapper || !document.body.contains(this.wrapper)) { this.createWrapper(); } this.wrapper.appendChild(child); return child; } // ---------------------------------------------------------------- // Backdrop // ---------------------------------------------------------------- createBackdrop() { this.backdrop = this.createWrapperChild(null, 'silktide-backdrop'); // prevent Lenis from hijacking wheel/touch on backdrop (Safari-friendly) this.backdrop.setAttribute('data-lenis-prevent', ''); this.backdrop.setAttribute('data-lenis-prevent-wheel', ''); this.backdrop.setAttribute('data-lenis-prevent-touch', ''); } showBackdrop() { if (this.backdrop) this.backdrop.style.display = 'block'; if (typeof this.config.onBackdropOpen === 'function') { this.config.onBackdropOpen(); } } hideBackdrop() { if (this.backdrop) this.backdrop.style.display = 'none'; if (typeof this.config.onBackdropClose === 'function') { this.config.onBackdropClose(); } } shouldShowBackdrop() { return this.config?.background?.showBackground || false; } // ---------------------------------------------------------------- // Storage + checkbox sync // ---------------------------------------------------------------- updateCheckboxState(saveToStorage = false) { if (!this.modal) return; const preferencesSection = this.modal.querySelector('#cookie-preferences'); if (!preferencesSection) return; const checkboxes = preferencesSection.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach((checkbox) => { const [, cookieId] = checkbox.id.split('cookies-'); const cookieType = this.config.cookieTypes.find((type) => type.id === cookieId); if (!cookieType) return; if (saveToStorage) { const currentState = checkbox.checked; if (cookieType.required) { localStorage.setItem(`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`, 'true'); } else { localStorage.setItem( `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`, currentState.toString(), ); if (currentState && typeof cookieType.onAccept === 'function') { cookieType.onAccept(); } else if (!currentState && typeof cookieType.onReject === 'function') { cookieType.onReject(); } } } else { if (cookieType.required) { checkbox.checked = true; checkbox.disabled = true; } else { const storedValue = localStorage.getItem( `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`, ); if (storedValue !== null) { checkbox.checked = storedValue === 'true'; } else { checkbox.checked = !!cookieType.defaultValue; } } } }); } setInitialCookieChoiceMade() { window.localStorage.setItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`, 1); } hasSetInitialCookieChoices() { return !!localStorage.getItem(`silktideCookieBanner_InitialChoice${this.getBannerSuffix()}`); } // ---------------------------------------------------------------- // Consent Handling // ---------------------------------------------------------------- handleCookieChoice(accepted) { this.setInitialCookieChoiceMade(); this.setCooldownOneDay(); this.removeBanner(); this.hideBackdrop(); if (this.modal) { this.modal.style.display = 'none'; } // keep cookie icon hidden after accept/reject this.hideCookieIcon(); this.allowBodyScroll(); this.config.cookieTypes.forEach((type) => { if (type.required === true) { localStorage.setItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`, 'true'); if (typeof type.onAccept === 'function') type.onAccept(); return; } localStorage.setItem( `silktideCookieChoice_${type.id}${this.getBannerSuffix()}`, accepted.toString(), ); if (accepted) { if (typeof type.onAccept === 'function') type.onAccept(); } else { if (typeof type.onReject === 'function') type.onReject(); } }); if (accepted && typeof this.config.onAcceptAll === 'function') { this.config.onAcceptAll(); } else if (!accepted && typeof this.config.onRejectAll === 'function') { this.config.onRejectAll(); } this.updateCheckboxState(false); } getAcceptedCookies() { return (this.config.cookieTypes || []).reduce((acc, cookieType) => { acc[cookieType.id] = localStorage.getItem(`silktideCookieChoice_${cookieType.id}${this.getBannerSuffix()}`) === 'true'; return acc; }, {}); } runAcceptedCookieCallbacks() { if (!this.config.cookieTypes) return; const acceptedCookies = this.getAcceptedCookies(); this.config.cookieTypes.forEach((type) => { if (type.required) return; if (acceptedCookies[type.id] && typeof type.onAccept === 'function') { type.onAccept(); } }); } runStoredCookiePreferenceCallbacks() { if (!this.config.cookieTypes) return; this.config.cookieTypes.forEach((type) => { const accepted = localStorage.getItem(`silktideCookieChoice_${type.id}${this.getBannerSuffix()}`) === 'true'; if (accepted) { if (typeof type.onAccept === 'function') type.onAccept(); } else { if (typeof type.onReject === 'function') type.onReject(); } }); } loadRequiredCookies() { if (!this.config.cookieTypes) return; this.config.cookieTypes.forEach((cookie) => { if (cookie.required && typeof cookie.onAccept === 'function') { cookie.onAccept(); } }); } // ---------------------------------------------------------------- // Banner // ---------------------------------------------------------------- getBannerContent() { const bannerDescription = this.config.text?.banner?.description || "

We use cookies on our site to enhance your user experience, provide personalized content, and analyze our traffic.

"; const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || 'Accept all'; const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel; const acceptAllButton = ``; const rejectNonEssentialButtonText = this.config.text?.banner?.rejectNonEssentialButtonText || 'Reject non-essential'; const rejectNonEssentialButtonLabel = this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel; const rejectNonEssentialButton = ``; const preferencesButtonText = this.config.text?.banner?.preferencesButtonText || 'Preferences'; const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel; const preferencesButton = ``; const silktideLogo = ` `; return ` ${bannerDescription}
${acceptAllButton} ${rejectNonEssentialButton}
${preferencesButton} ${silktideLogo}
`; } createBanner() { this.banner = this.createWrapperChild(this.getBannerContent(), 'silktide-banner'); if (this.banner && this.config.position?.banner) { this.banner.classList.add(this.config.position.banner); } if (this.banner && typeof this.config.onBannerOpen === 'function') { this.config.onBannerOpen(); } } removeBanner() { if (this.banner && this.banner.parentNode) { this.banner.parentNode.removeChild(this.banner); this.banner = null; if (typeof this.config.onBannerClose === 'function') { this.config.onBannerClose(); } } } shouldShowBanner() { if (this.config.showBanner === false) return false; if (!this.hasSetInitialCookieChoices()) return true; return !this.isInCooldown(); } // ---------------------------------------------------------------- // Modal // ---------------------------------------------------------------- getModalContent() { const preferencesTitle = this.config.text?.preferences?.title || 'Customize your cookie preferences'; const preferencesDescription = this.config.text?.preferences?.description || "

We respect your right to privacy. You can choose not to allow some types of cookies. Your cookie preferences will apply across our website.

"; const preferencesButtonLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel; const closeModalButton = ``; const cookieTypes = this.config.cookieTypes || []; const acceptedCookieMap = this.getAcceptedCookies(); const acceptAllButtonText = this.config.text?.banner?.acceptAllButtonText || 'Accept all'; const acceptAllButtonLabel = this.config.text?.banner?.acceptAllButtonAccessibleLabel; const acceptAllButton = ``; const rejectNonEssentialButtonText = this.config.text?.banner?.rejectNonEssentialButtonText || 'Reject non-essential'; const rejectNonEssentialButtonLabel = this.config.text?.banner?.rejectNonEssentialButtonAccessibleLabel; const rejectNonEssentialButton = ``; const creditLinkText = this.config.text?.preferences?.creditLinkText || 'Get this banner for free'; const creditLinkAccessibleLabel = this.config.text?.preferences?.creditLinkAccessibleLabel; const creditLink = creditLinkText ? `${creditLinkText}` : ''; const switchOnText = this.config.text?.switch?.on || 'On'; const switchOffText = this.config.text?.switch?.off || 'Off'; return `

${preferencesTitle}

${closeModalButton}
${preferencesDescription} `; } createModal() { this.modal = this.createWrapperChild(this.getModalContent(), 'silktide-modal'); this.modal.setAttribute('data-lenis-prevent', ''); this.modal.setAttribute('data-lenis-prevent-wheel', ''); this.modal.setAttribute('data-lenis-prevent-touch', ''); } toggleModal(show) { if (!this.modal) return; this.modal.style.display = show ? 'flex' : 'none'; if (show) { this.showBackdrop(); this.hideCookieIcon(); this.removeBanner(); this.preventBodyScroll(); const modalCloseButton = this.modal.querySelector('.modal-close'); modalCloseButton?.focus(); if (typeof this.config.onPreferencesOpen === 'function') { this.config.onPreferencesOpen(); } this.updateCheckboxState(false); } else { this.setInitialCookieChoiceMade(); this.setCooldownOneDay(); this.updateCheckboxState(true); this.hideBackdrop(); this.hideCookieIcon(); this.allowBodyScroll(); if (typeof this.config.onPreferencesClose === 'function') { this.config.onPreferencesClose(); } } } // ---------------------------------------------------------------- // Cookie Icon // ---------------------------------------------------------------- getCookieIconContent() { return ` `; } createCookieIcon() { this.cookieIcon = document.createElement('button'); this.cookieIcon.id = 'silktide-cookie-icon'; this.cookieIcon.title = 'Manage your cookie preferences for this site'; this.cookieIcon.innerHTML = this.getCookieIconContent(); if (this.config.text?.banner?.preferencesButtonAccessibleLabel) { this.cookieIcon.ariaLabel = this.config.text?.banner?.preferencesButtonAccessibleLabel; } if (!this.wrapper || !document.body.contains(this.wrapper)) { this.createWrapper(); } this.wrapper.appendChild(this.cookieIcon); if (this.cookieIcon && this.config.cookieIcon?.position) { this.cookieIcon.classList.add(this.config.cookieIcon.position); } if (this.cookieIcon && this.config.cookieIcon?.colorScheme) { this.cookieIcon.classList.add(this.config.cookieIcon.colorScheme); } // ✅ HARD HIDE immediately (beats any CSS default display) this.cookieIcon.style.display = 'none'; } showCookieIcon() { if (this.cookieIcon) this.cookieIcon.style.display = 'flex'; } hideCookieIcon() { if (this.cookieIcon) this.cookieIcon.style.display = 'none'; } // ---------------------------------------------------------------- // Focus trap helpers // ---------------------------------------------------------------- getFocusableElements(element) { return element.querySelectorAll( 'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); } setupEventListeners() { if (this.banner) { const acceptButton = this.banner.querySelector('.accept-all'); const rejectButton = this.banner.querySelector('.reject-all'); const preferencesButton = this.banner.querySelector('.preferences'); acceptButton?.addEventListener('click', () => this.handleCookieChoice(true)); rejectButton?.addEventListener('click', () => this.handleCookieChoice(false)); preferencesButton?.addEventListener('click', () => { this.showBackdrop(); this.toggleModal(true); }); const focusableElements = this.getFocusableElements(this.banner); const firstFocusableEl = focusableElements[0]; const lastFocusableEl = focusableElements[focusableElements.length - 1]; this.banner.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey) { if (document.activeElement === firstFocusableEl) { lastFocusableEl.focus(); e.preventDefault(); } } else { if (document.activeElement === lastFocusableEl) { firstFocusableEl.focus(); e.preventDefault(); } } } }); if (this.config.mode !== 'wizard') { acceptButton?.focus(); } } if (this.modal) { const closeButton = this.modal.querySelector('.modal-close'); const acceptAllButton = this.modal.querySelector('.preferences-accept-all'); const rejectAllButton = this.modal.querySelector('.preferences-reject-all'); closeButton?.addEventListener('click', () => { this.toggleModal(false); // no icon after close; cooldown already set in toggleModal(false) }); acceptAllButton?.addEventListener('click', () => this.handleCookieChoice(true)); rejectAllButton?.addEventListener('click', () => this.handleCookieChoice(false)); const focusableElements = this.getFocusableElements(this.modal); const firstFocusableEl = focusableElements[0]; const lastFocusableEl = focusableElements[focusableElements.length - 1]; this.modal.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey) { if (document.activeElement === firstFocusableEl) { lastFocusableEl.focus(); e.preventDefault(); } } else { if (document.activeElement === lastFocusableEl) { firstFocusableEl.focus(); e.preventDefault(); } } } if (e.key === 'Escape') { this.toggleModal(false); } }); closeButton?.focus(); const preferencesSection = this.modal.querySelector('#cookie-preferences'); const checkboxes = preferencesSection?.querySelectorAll('input[type="checkbox"]') || []; checkboxes.forEach((checkbox) => { checkbox.addEventListener('change', (event) => { const [, cookieId] = event.target.id.split('cookies-'); const isAccepted = event.target.checked; const previousValue = localStorage.getItem(`silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`) === 'true'; if (isAccepted !== previousValue) { const cookieType = this.config.cookieTypes.find((type) => type.id === cookieId); if (cookieType) { localStorage.setItem( `silktideCookieChoice_${cookieId}${this.getBannerSuffix()}`, isAccepted.toString(), ); if (isAccepted && typeof cookieType.onAccept === 'function') { cookieType.onAccept(); } else if (!isAccepted && typeof cookieType.onReject === 'function') { cookieType.onReject(); } } } }); }); } if (this.cookieIcon) { this.cookieIcon.addEventListener('click', () => { if (!this.modal) { this.createModal(); this.toggleModal(true); this.hideCookieIcon(); } else if (this.modal.style.display === 'none' || this.modal.style.display === '') { this.toggleModal(true); this.hideCookieIcon(); } else { this.toggleModal(false); } }); } } getBannerSuffix() { if (this.config.bannerSuffix) return '_' + this.config.bannerSuffix; return ''; } // ---------------------------------------------------------------- // Lenis + Safari friendly lock // ---------------------------------------------------------------- preventBodyScroll() { if (window.lenis && typeof window.lenis.stop === 'function') { window.lenis.stop(); } document.documentElement.style.overflow = 'hidden'; document.body.style.overflow = 'hidden'; } allowBodyScroll() { document.documentElement.style.overflow = ''; document.body.style.overflow = ''; if (window.lenis && typeof window.lenis.start === 'function') { window.lenis.start(); if (typeof window.lenis.resize === 'function') { window.lenis.resize(); } } } } (function () { window.silktideCookieBannerManager = {}; let config = {}; let cookieBanner; function updateCookieBannerConfig(userConfig = {}) { config = { ...config, ...userConfig }; if (cookieBanner) { cookieBanner.destroyCookieBanner(); cookieBanner = null; } if (document.body) { initCookieBanner(); } else { document.addEventListener('DOMContentLoaded', initCookieBanner, { once: true }); } } function initCookieBanner() { if (!cookieBanner) { cookieBanner = new SilktideCookieBanner(config); } } function injectScript(url, loadOption) { const existingScript = document.querySelector(`script[src="${url}"]`); if (existingScript) return; const script = document.createElement('script'); script.src = url; if (loadOption === 'async') script.async = true; else if (loadOption === 'defer') script.defer = true; document.head.appendChild(script); } window.silktideCookieBannerManager.initCookieBanner = initCookieBanner; window.silktideCookieBannerManager.updateCookieBannerConfig = updateCookieBannerConfig; window.silktideCookieBannerManager.injectScript = injectScript; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initCookieBanner, { once: true }); } else { initCookieBanner(); } })();