1// Shoporama Cookie Consent Interceptor v1.0 2// External script version 3// 4// GDPR Compliance Cookie Interceptor: 5// Dette script overskriver document.cookie på prototype-niveau for at blokere 6// alle cookies indtil brugeren giver eksplicit samtykke. Nødvendige cookies 7// (kurv, login, etc.) tillades altid via whitelist. Andre cookies kategoriseres 8// automatisk som Analytics eller Marketing baseret på navn-patterns og blokeres 9// indtil relevant samtykke gives. Blokerede cookies gemmes i hukommelsen og 10// sættes først når/hvis brugeren accepterer den pågældende kategori. 11// Google Consent Mode v2 opdateres automatisk baseret på brugerens valg. 12// 13(function() { 14 'use strict'; 15 16 // Get configuration from global variable set by Shoporama 17 var config = window.shoporamaCookieConfig || {}; 18 19 // Ensure default values 20 config.consentCookieName = config.consentCookieName || 'shoporama_consent'; 21 config.consentDuration = config.consentDuration || 365; 22 23 // Check multiple sources for display mode configuration: 24 // 1. Direct config object (if Shoporama sets it) 25 // 2. Global variable that Shoporama might set 26 // 3. Data attribute on script tag 27 // 4. Default to overlay 28 29 // Try to get from global variable first 30 if (typeof window.SHOPORAMA_COOKIE_DISPLAY_MODE !== 'undefined') { 31 config.displayMode = window.SHOPORAMA_COOKIE_DISPLAY_MODE; 32 } 33 // Try to get from data attribute on current script tag 34 else if (document.currentScript && document.currentScript.dataset.displayMode) { 35 config.displayMode = document.currentScript.dataset.displayMode; 36 } 37 // Default to overlay 38 else if (!config.displayMode) { 39 config.displayMode = 'overlay'; 40 } 41 42 // Default texts if not provided 43 config.texts = config.texts || { 44 bannerText: 'Vi bruger cookies til at forbedre din oplevelse på webshoppen.', 45 readMore: 'Læs mere', 46 acceptAll: 'Accepter alle', 47 settings: 'Indstillinger', 48 reject: 'Afvis alle', 49 settingsTitle: 'Cookie indstillinger', 50 settingsText: 'Vælg hvilke cookies du vil tillade:', 51 necessary: 'Nødvendige cookies (kan ikke fravælges)', 52 analytics: 'Statistik cookies', 53 analyticsDesc: 'Hjælper os med at forstå hvordan webshoppen bruges', 54 marketing: 'Marketing cookies', 55 marketingDesc: 'Bruges til at vise relevante annoncer', 56 save: 'Gem valg', 57 cancel: 'Tilbage' 58 }; 59 60 // Default policy content if not provided 61 config.policyContent = config.policyContent || ''; 62 63 // Gem original cookie descriptor 64 var originalCookie = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie'); 65 66 // Nødvendige cookies der altid tillades 67 var cookieWhitelist = [ 68 'basket', 'customer_token', 'user_token', 'language', 69 'unpaid_order_id', 'shoporama_consent', 'currency', 'PHPSESSID', 70 'st', 'ref' 71 ]; 72 73 // Prefix whitelist (funktionelle cookies med dynamisk suffix) 74 var prefixWhitelist = ['np_closed_']; 75 76 // Gem blokerede cookies til senere 77 var blockedCookies = {}; 78 var blockedLocalStorage = {}; 79 80 // Check for existing consent 81 function getConsent() { 82 var cookies = originalCookie.get.call(document).split(';'); 83 for (var i = 0; i < cookies.length; i++) { 84 var cookie = cookies[i].trim(); 85 if (cookie.indexOf(config.consentCookieName + '=') === 0) { 86 var value = cookie.substring(config.consentCookieName.length + 1); 87 if (value) { 88 try { 89 return JSON.parse(decodeURIComponent(value)); 90 } catch(e) { 91 // Ignore invalid consent cookie 92 } 93 } 94 } 95 } 96 return null; 97 } 98 99 // Public consent API for apps 100 // Apps can use: shoporamaConsent.onConsent('statistics', load) 101 // or: shoporamaConsent.onConsent(['statistics', 'marketing'], load) 102 var existingConsent = getConsent(); 103 window.shoporamaConsent = { 104 categories: { 105 necessary: true, 106 preferences: false, 107 statistics: existingConsent ? existingConsent.analytics === true : false, 108 marketing: existingConsent ? existingConsent.marketing === true : false 109 }, 110 111 isAllowed: function(category) { 112 return this.categories[category] === true; 113 }, 114 115 grant: function(category) { 116 this.categories[category] = true; 117 dispatchEvent(new CustomEvent('shoporama:consent:' + category)); 118 }, 119 120 onConsent: function(categories, callback) { 121 categories = Array.isArray(categories) ? categories : [categories]; 122 var self = this; 123 var remaining = categories.filter(function(c) { return !self.isAllowed(c); }); 124 125 if (remaining.length === 0) { 126 callback(); 127 } 128 else { 129 remaining.forEach(function(category) { 130 addEventListener('shoporama:consent:' + category, function() { 131 remaining = remaining.filter(function(c) { return c !== category; }); 132 if (remaining.length === 0) callback(); 133 }, { once: true });
134 }); 135 } 136 } 137 }; 138 139 // Kategoriser cookie baseret på navn 140 function categorizeCookie(cookieName) { 141 // Analytics cookies - Google, Adobe, Hotjar, Mixpanel, etc. 142 if (cookieName.match(/^(_ga|_gid|_gat|_gac_|_gcl_|gtm_|_gtm|_gaq|__utm|_hjid|_hjfirstseen|_hjincludedinsample|_hj|hj|mp_|__mp|_omappvp|_omappvs|s_|sc_|_tq|ajs_|_dc_gtm|_fbq)/i)) { 143 return 'analytics'; 144 } 145 146 // Marketing cookies - Facebook, LinkedIn, Pinterest, TikTok, Snapchat, Twitter/X, etc. 147 if (cookieName.match(/^(_fbp|_fbc|fr|tr|sb|c_user|_pin_|_pinterest_|_linkedin_|li_|bcookie|bscookie|lidc|_form_|ac_|vuid|__ar_|mc_|partnerads|pa_|pacid|cFPID|_ttp|_tt_enable_cookie|_uetsid|_uetvid|MUID|_clck|_clsk|__kla_|klaviyo|_gcl_aw|_gcl_dc|_schn|sc_at|personalization_id|guest_id|_twitter_sess)/i)) { 148 return 'marketing'; 149 } 150 151 // Cookie consent management platforms 152 if (cookieName.match(/^(cookiebot|CookieConsent|euconsent|consentUUID|_cmpRepromptHash|OptanonConsent|OptanonAlertBoxClosed)/i)) { 153 return 'necessary'; 154 } 155 156 // Chat/Support widgets - Intercom, Zendesk, Drift, etc. 157 if (cookieName.match(/^(intercom|_hjSessionUser|_hjSession|drift_|__zlcmid|__zlcstore|zd-store|__hssc|__hssrc|__hstc|hubspotutk)/i)) { 158 return 'marketing'; 159 } 160 161 // A/B Testing platforms - Optimizely, VWO, etc. 162 if (cookieName.match(/^(optimizelyEndUserId|optimizelySegments|optimizelyBuckets|_vis_opt_|_vwo_)/i)) { 163 return 'analytics'; 164 } 165 166 return 'unknown'; 167 } 168 169 // Override document.cookie setter 170 Object.defineProperty(document, 'cookie', { 171 get: function() { 172 return originalCookie.get.call(this); 173 }, 174 set: function(value) { 175 // Parse cookie name 176 var cookieName = value.split('=')[0].trim(); 177 178 // Check whitelist 179 var isWhitelisted = cookieWhitelist.some(function(allowed) { 180 return cookieName === allowed; 181 }); 182 183 // Always allow whitelisted cookies 184 if (isWhitelisted) { 185 return originalCookie.set.call(this, value); 186 } 187 188 // Check prefix whitelist 189 var isPrefixWhitelisted = prefixWhitelist.some(function(prefix) { 190 return cookieName.indexOf(prefix) === 0; 191 }); 192 if (isPrefixWhitelisted) { 193 return originalCookie.set.call(this, value); 194 } 195 196 // Get current consent 197 var consent = getConsent(); 198 199 // If no consent, block and store 200 if (!consent) { 201 blockedCookies[cookieName] = value; 202 return; 203 } 204 205 // Categorize cookie 206 var category = categorizeCookie(cookieName); 207 208 // Check category consent 209 if (category === 'analytics' && !consent.analytics) { 210 blockedCookies[cookieName] = value; 211 return; 212 } 213 214 // Treat unknown cookies as marketing cookies 215 if ((category === 'marketing' || category === 'unknown') && !consent.marketing) { 216 blockedCookies[cookieName] = value; 217 return; 218 } 219 220 // Cookie is allowed 221 return originalCookie.set.call(this, value); 222 }, 223 configurable: true 224 }); 225 226 // Override localStorage for tracking keys 227 var originalSetItem = Storage.prototype.setItem; 228 Storage.prototype.setItem = function(key, value) { 229 var consent = getConsent(); 230 231 // Expanded list of known tracking localStorage keys 232 var trackingKeys = /^(_ga|_gid|_fbp|_fbc|_pin|linkedin|klaviyo|hj|_hj|ab_|optimizely|gtm|_gtm|amplitude|mixpanel|mp_|intercom|drift|_vwo|_vis_opt|_hjSessionUser|_hjSession|__mp)/i; 233 234 if (!consent && trackingKeys.test(key)) { 235 blockedLocalStorage[key] = value; 236 return; 237 } 238 239 return originalSetItem.call(this, key, value); 240 }; 241 242 // Create and inject styles 243 function injectStyles() { 244 // Load external CSS 245 var link = document.createElement('link'); 246 link.rel = 'stylesheet'; 247 link.href = '/external/cookie-popup/cookie-consent.css'; 248 document.head.appendChild(link); 249 250 // Add color customization CSS 251 if (config.colors) { 252 var colorCSS = ` 253 #shoporama-cookie-banner .accept,
254 #shoporama-cookie-settings .accept { 255 background: ${config.colors.acceptColor} !important; 256 border-color: ${config.colors.acceptColor} !important; 257 } 258 #shoporama-cookie-banner .accept:hover, 259 #shoporama-cookie-settings .accept:hover { 260 background: ${config.colors.acceptHoverColor} !important; 261 border-color: ${config.colors.acceptHoverColor} !important; 262 } 263 #shoporama-cookie-banner .reject, 264 #shoporama-cookie-settings .reject { 265 background: ${config.colors.rejectColor} !important; 266 border-color: ${config.colors.rejectColor} !important; 267 } 268 #shoporama-cookie-banner .reject:hover, 269 #shoporama-cookie-settings .reject:hover { 270 background: ${config.colors.rejectHoverColor} !important; 271 border-color: ${config.colors.rejectHoverColor} !important; 272 } 273 `; 274 var colorStyle = document.createElement('style'); 275 colorStyle.textContent = colorCSS; 276 document.head.appendChild(colorStyle); 277 } 278 279 // Add custom CSS if provided 280 if (config.customCSS) { 281 var style = document.createElement('style'); 282 style.textContent = config.customCSS; 283 document.head.appendChild(style); 284 } 285 } 286 287 // Create banner HTML 288 function createBanner() { 289 // Remove consent check - we want to show banner when user clicks button 290 // if (getConsent()) return; 291 292 injectStyles(); 293 294 var isSticky = config.displayMode === 'sticky'; 295 var overlay, bannerContainer, banner; 296 297 if (isSticky) { 298 // Sticky bar mode - no overlay needed 299 banner = document.createElement('div'); 300 banner.id = 'shoporama-cookie-banner'; 301 banner.className = 'sticky-mode'; 302 banner.innerHTML = ` 303 <div class="sticky-content"> 304 <div class="sticky-text"> 305 <p>${config.texts.bannerText}</p> 306 <a href="#" onclick="shoporamaCookies.showPolicy(); return false;">${config.texts.readMore}</a> 307 </div> 308 <div class="sticky-buttons"> 309 <button class="accept" onclick="shoporamaCookies.accept()">${config.texts.acceptAll}</button> 310 <button class="reject" onclick="shoporamaCookies.reject()">${config.texts.reject}</button> 311 <button class="settings" onclick="shoporamaCookies.showSettings()">${config.texts.settings}</button> 312 </div> 313 </div> 314 `; 315 } else { 316 // Classic overlay mode 317 overlay = document.createElement('div'); 318 overlay.id = 'shoporama-cookie-overlay'; 319 320 bannerContainer = document.createElement('div'); 321 bannerContainer.id = 'shoporama-cookie-banner-container'; 322 323 // Add click handler to shake banner when clicking outside 324 bannerContainer.onclick = function(e) { 325 if (e.target === bannerContainer) { 326 var banner = document.getElementById('shoporama-cookie-banner'); 327 if (banner) { 328 banner.classList.add('shake'); 329 setTimeout(function() { 330 banner.classList.remove('shake'); 331 }, 300); 332 } 333 } 334 }; 335 336 banner = document.createElement('div'); 337 banner.id = 'shoporama-cookie-banner'; 338 banner.innerHTML = ` 339 <div class="banner-text"> 340 <h3>${config.texts.bannerTitle || 'Dette website bruger cookies'}</h3> 341 <p>${config.texts.bannerText}</p> 342 <p>${config.texts.bannerSubtext || 'Oplysninger om din brug af vores hjemmeside deles også med vores partnere inden for sociale medier, annoncering, og analyse, som kan kombinere dem med andre data fra dine interaktioner med deres tjenester.'}</p> 343 <p> 344 <a href="#" onclick="shoporamaCookies.showPolicy(); return false;">${config.texts.readMore}</a> 345 </p> 346 </div> 347 <div class="buttons"> 348 <button class="accept" onclick="shoporamaCookies.accept()">${config.texts.acceptAll}</button> 349 <button class="reject" onclick="shoporamaCookies.reject()">${config.texts.reject}</button> 350 <button class="settings" onclick="shoporamaCookies.showSettings()">${config.texts.settings}</button> 351 </div> 352 `; 353 354 bannerContainer.appendChild(banner); 355 } 356 357 // Create policy modal 358 var policyModal = document.createElement('div'); 359 policyModal.id = 'shoporama-cookie-policy'; 360 policyModal.className = 'cookie-modal'; 361 policyModal.innerHTML = ` 362 <div class="modal-header"> 363 <h3>Cookie Politik</h3> 364 </div> 365 <div class="modal-body"> 366 ${config.policyContent || '<p>Cookie politik indhold ikke tilgængeligt.</p>'} 367 </div> 368 <div class="modal-footer"> 369 <button class="back" onclick="shoporamaCookies.hidePolicy()"><span style="display: inline-flex; align-items: center; gap: 0.5rem;">⹠Tilbage</span></button> 370 </div> 371 `; 372 373 // Create settings modal 374 var settings = document.createElement('div');
375 settings.id = 'shoporama-cookie-settings'; 376 377 // Add click handler for settings modal background (only for overlay mode) 378 if (!isSticky && overlay) { 379 overlay.addEventListener('click', function(e) { 380 if (e.target === overlay && settings.style.display !== 'none') { 381 settings.classList.add('shake'); 382 setTimeout(function() { 383 settings.classList.remove('shake'); 384 }, 300); 385 } 386 }); 387 } 388 389 settings.innerHTML = ` 390 <h3>${config.texts.settingsTitle}</h3> 391 392 <div class="cookie-groups"> 393 <!-- Necessary Cookies --> 394 <div class="cookie-group"> 395 <div class="cookie-group-header"> 396 <div class="cookie-info"> 397 <h4>${config.texts.necessary}</h4> 398 <p>Disse cookies er nødvendige for at websitet kan fungere</p> 399 </div> 400 <div class="toggle-switch disabled"> 401 <input type="checkbox" checked disabled> 402 <span class="toggle-slider"></span> 403 </div> 404 </div> 405 </div> 406 407 <!-- Analytics Cookies --> 408 <div class="cookie-group"> 409 <div class="cookie-group-header"> 410 <div class="cookie-info"> 411 <h4>${config.texts.analytics}</h4> 412 <p>${config.texts.analyticsDesc}</p> 413 </div> 414 <label class="toggle-switch"> 415 <input type="checkbox" id="consent-analytics"> 416 <span class="toggle-slider"></span> 417 </label> 418 </div> 419 </div> 420 421 <!-- Marketing Cookies --> 422 <div class="cookie-group"> 423 <div class="cookie-group-header"> 424 <div class="cookie-info"> 425 <h4>${config.texts.marketing}</h4> 426 <p>${config.texts.marketingDesc}</p> 427 </div> 428 <label class="toggle-switch"> 429 <input type="checkbox" id="consent-marketing"> 430 <span class="toggle-slider"></span> 431 </label> 432 </div> 433 </div> 434 </div> 435 436 <div class="button-group"> 437 <button class="back" onclick="shoporamaCookies.hideSettings()"><span style="display: inline-flex; align-items: center; gap: 0.5rem;">⹠${config.texts.cancel || 'Tilbage'}</span></button> 438 <div class="save-buttons"> 439 <button class="reject" onclick="shoporamaCookies.reject()">${config.texts.reject}</button> 440 <button class="save" onclick="shoporamaCookies.saveSettings()">${config.texts.save || 'Gem indstillinger'}</button> 441 <button class="accept" onclick="shoporamaCookies.accept()">${config.texts.acceptAll}</button> 442 </div> 443 </div> 444 `; 445 446 // Add to DOM when ready 447 function addToDOM() { 448 if (isSticky) { 449 // Sticky mode - just add banner and modals 450 document.body.appendChild(banner); 451 document.body.appendChild(settings); 452 document.body.appendChild(policyModal); 453 454 // Create overlay for modals only 455 var modalOverlay = document.createElement('div'); 456 modalOverlay.id = 'shoporama-cookie-overlay'; 457 modalOverlay.style.display = 'none'; 458 document.body.appendChild(modalOverlay); 459 460 // Show banner immediately 461 setTimeout(function() { 462 banner.classList.add('show'); 463 }, 100); 464 } else { 465 // Overlay mode - add all elements 466 document.body.appendChild(overlay); 467 document.body.appendChild(bannerContainer); 468 document.body.appendChild(settings);
469 document.body.appendChild(policyModal); 470 } 471 472 // Show overlay with fade effect (only for overlay mode) 473 setTimeout(function() { 474 if (!isSticky && overlay) { 475 overlay.style.display = 'block'; 476 } 477 setTimeout(function() { 478 overlay.classList.add('show'); 479 }, 10); 480 }, 10); 481 } 482 483 if (document.body) { 484 addToDOM(); 485 } else { 486 document.addEventListener('DOMContentLoaded', addToDOM); 487 } 488 } 489 490 // Process blocked cookies after consent 491 function processBlockedCookies(consent) { 492 // Re-set allowed cookies 493 for (var cookieName in blockedCookies) { 494 var category = categorizeCookie(cookieName); 495 var allowed = false; 496 497 if (category === 'analytics' && consent.analytics) allowed = true; 498 if (category === 'marketing' && consent.marketing) allowed = true; 499 if (category === 'unknown' && (consent.analytics || consent.marketing)) allowed = true; 500 501 if (allowed) { 502 originalCookie.set.call(document, blockedCookies[cookieName]); 503 } 504 } 505 506 // Process blocked localStorage 507 for (var key in blockedLocalStorage) { 508 if (consent.analytics || consent.marketing) { 509 originalSetItem.call(localStorage, key, blockedLocalStorage[key]); 510 } 511 } 512 513 // Clear blocked storage 514 blockedCookies = {}; 515 blockedLocalStorage = {}; 516 } 517 518 // Initialize default consent states for Google Consent Mode v2 519 function initDefaultConsentMode() { 520 // Ensure dataLayer exists 521 window.dataLayer = window.dataLayer || []; 522 523 // Define gtag function if it doesn't exist 524 if (typeof window.gtag !== 'function') { 525 window.gtag = function() { 526 window.dataLayer.push(arguments); 527 }; 528 } 529 530 // Set default consent states (denied by default for GDPR compliance) 531 window.gtag('consent', 'default', { 532 'analytics_storage': 'denied', 533 'ad_storage': 'denied', 534 'ad_user_data': 'denied', 535 'ad_personalization': 'denied', 536 'functionality_storage': 'granted', 537 'security_storage': 'granted', 538 'wait_for_update': 2000 // Wait up to 2 seconds for user consent 539 }); 540 541 // Also set region-specific defaults for EU/UK (optional but recommended) 542 window.gtag('consent', 'default', { 543 'analytics_storage': 'denied', 544 'ad_storage': 'denied', 545 'ad_user_data': 'denied', 546 'ad_personalization': 'denied', 547 'region': ['AD', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', '
547SE', 'GB', 'CH'], 548 'wait_for_update': 2000 549 }); 550 } 551 552 // Update Google Consent Mode 553 function updateConsentMode(consent) { 554 // Ensure gtag is available 555 if (typeof window.gtag !== 'function') { 556 window.dataLayer = window.dataLayer || []; 557 window.gtag = function() { 558 window.dataLayer.push(arguments); 559 }; 560 } 561 562 // Update consent states based on user choice 563 window.gtag('consent', 'update', { 564 'analytics_storage': consent.analytics ? 'granted' : 'denied', 565 'ad_storage': consent.marketing ? 'granted' : 'denied', 566 'ad_user_data': consent.marketing ? 'granted' : 'denied', 567 'ad_personalization': consent.marketing ? 'granted' : 'denied' 568 }); 569 570 // Trigger dataLayer event 571 if (window.dataLayer) { 572 window.dataLayer.push({ 573 'event': 'cookie_consent_update', 574 'consent_analytics': consent.analytics, 575 'consent_marketing': consent.marketing 576 }); 577 } 578 } 579 580 // Public API 581 window.shoporamaCookies = { 582 accept: function() { 583 this.setConsent({ analytics: true, marketing: true }); 584 }, 585 586 reject: function() { 587 this.setConsent({ analytics: false, marketing: false }); 588 }, 589 590 showSettings: function() { 591 var isSticky = config.displayMode === 'sticky'; 592 var banner = document.getElementById('shoporama-cookie-banner'); 593 var bannerContainer = document.getElementById('shoporama-cookie-banner-container'); 594 var modal = document.getElementById('shoporama-cookie-settings'); 595 var overlay = document.getElementById('shoporama-cookie-overlay'); 596 597 if (modal) { 598 // Load current preferences 599 var consent = getConsent(); 600 if (consent) { 601 document.getElementById('consent-analytics').checked = consent.analytics; 602 document.getElementById('consent-marketing').checked = consent.marketing; 603 } 604 605 // Hide banner and show settings 606 if (isSticky && banner) { 607 banner.style.display = 'none'; 608 } else if (bannerContainer) { 609 bannerContainer.style.display = 'none'; 610 } 611 612 // Show overlay for modal 613 if (overlay) { 614 overlay.style.display = 'block'; 615 } 616 617 modal.style.display = 'block'; 618 } 619 }, 620 621 hideSettings: function() { 622 var isSticky = config.displayMode === 'sticky'; 623 var banner = document.getElementById('shoporama-cookie-banner'); 624 var bannerContainer = document.getElementById('shoporama-cookie-banner-container'); 625 var modal = document.getElementById('shoporama-cookie-settings'); 626 var overlay = document.getElementById('shoporama-cookie-overlay'); 627 628 if (modal) { 629 // Hide settings 630 modal.style.display = 'none'; 631 632 // Show banner again 633 if (isSticky && banner) { 634 banner.style.display = 'block'; 635 // Hide overlay for sticky mode 636 if (overlay) { 637 overlay.style.display = 'none'; 638 } 639 } else if (bannerContainer) { 640 bannerContainer.style.display = 'flex'; 641 // Keep overlay visible for overlay mode 642 } 643 } 644 }, 645 646 saveSettings: function() { 647 var analytics = document.getElementById('consent-analytics').checked; 648 var marketing = document.getElementById('consent-marketing').checked; 649 this.setConsent({ analytics: analytics, marketing: marketing });
650 this.hideSettings(); 651 }, 652 653 setConsent: function(consent) { 654 // Get previous consent to detect changes 655 var previousConsent = getConsent(); 656 657 // Add timestamp 658 consent.timestamp = new Date().toISOString(); 659 consent.version = '1.0'; 660 661 // If consent is being withdrawn (was true, now false), delete cookies 662 if (previousConsent) { 663 if (previousConsent.analytics && !consent.analytics) { 664 this.deleteAnalyticsCookies(); 665 } 666 if (previousConsent.marketing && !consent.marketing) { 667 this.deleteMarketingCookies(); 668 } 669 } 670 671 // Save consent cookie 672 var expires = new Date(); 673 expires.setTime(expires.getTime() + (config.consentDuration * 24 * 60 * 60 * 1000)); 674 675 originalCookie.set.call(document, config.consentCookieName + '=' + 676 encodeURIComponent(JSON.stringify(consent)) + 677 '; expires=' + expires.toUTCString() + 678 '; path=/; SameSite=Lax'); 679 680 // Process blocked cookies 681 processBlockedCookies(consent); 682 683 // Update consent mode 684 updateConsentMode(consent); 685 686 // Fire consent events for apps 687 if (consent.analytics) { 688 window.shoporamaConsent.grant('statistics'); 689 } 690 if (consent.marketing) { 691 window.shoporamaConsent.grant('marketing'); 692 } 693 694 // Hide overlay and containers 695 var isSticky = config.displayMode === 'sticky'; 696 var overlay = document.getElementById('shoporama-cookie-overlay'); 697 var banner = document.getElementById('shoporama-cookie-banner'); 698 var bannerContainer = document.getElementById('shoporama-cookie-banner-container'); 699 var settings = document.getElementById('shoporama-cookie-settings'); 700 701 if (overlay) overlay.style.display = 'none'; 702 if (isSticky && banner) { 703 banner.style.display = 'none'; 704 } else if (bannerContainer) { 705 bannerContainer.style.display = 'none'; 706 } 707 if (settings) settings.style.display = 'none'; 708 709 // Reload page to ensure all tracking scripts initialize properly 710 setTimeout(function() { 711 window.location.reload(); 712 }, 100); 713 }, 714 715 // Delete analytics cookies 716 deleteAnalyticsCookies: function() { 717 var analyticsCookies = [ 718 '_ga', '_gid', '_gat', '_gac_', '_gcl_', 'gtm_', '_gtm', '_gaq', '__utm', 719 '_hjid', '_hjfirstseen', '_hjincludedinsample', '_hj', 'hj', 720 'mp_', '__mp', '_omappvp', '_omappvs', 's_', 'sc_', '_tq', 'ajs_', 721 '_dc_gtm', '_fbq', 'optimizelyEndUserId', 'optimizelySegments', 722 'optimizelyBuckets', '_vis_opt_', '_vwo_' 723 ]; 724 725 this.deleteCookiesByPattern(analyticsCookies); 726 this.clearLocalStorageByPattern(analyticsCookies); 727 }, 728 729 // Delete marketing cookies 730 deleteMarketingCookies: function() { 731 var marketingCookies = [ 732 '_fbp', '_fbc', 'fr', 'tr', 'sb', 'c_user', '_pin_', '_pinterest_', 733 '_linkedin_', 'li_', 'bcookie', 'bscookie', 'lidc', '_form_', 'ac_', 734 'vuid', '__ar_', 'mc_', 'partnerads', 'pa_', 'pacid', 'cFPID', 735 '_ttp', '_tt_enable_cookie', '_uetsid', '_uetvid', 'MUID', '_clck', 736 '_clsk', '__kla_', 'klaviyo', '_gcl_aw', '_gcl_dc', '_schn', 'sc_at', 737 'personalization_id', 'guest_id', '_twitter_sess', 'intercom', 738 'drift_', '__zlcmid', '__zlcstore', 'zd-store', '__hssc', '__hssrc', 739 '__hstc', 'hubspotutk' 740 ]; 741 742 this.deleteCookiesByPattern(marketingCookies); 743 this.clearLocalStorageByPattern(marketingCookies); 744 }, 745 746 // Helper function to delete cookies by pattern 747 deleteCookiesByPattern: function(patterns) { 748 // Get all cookies 749 var cookies = document.cookie.split(';'); 750 751 for (var i = 0; i < cookies.length; i++) { 752 var cookie = cookies[i].trim(); 753 var cookieName = cookie.split('=')[0]; 754 755 // Check if cookie matches any pattern 756 for (var j = 0; j < patterns.length; j++) { 757 if (cookieName.indexOf(patterns[j]) === 0) { 758 // Delete cookie for current domain and all parent domains 759 this.deleteCookie(cookieName); 760 break; 761 } 762 } 763 } 764 }, 765 766 // Delete a specific cookie 767 deleteCookie: function(name) { 768 // Try to delete for various domain/path combinations 769 var pathBits = location.pathname.split('/'); 770 var pathCurrent = '/'; 771 772 // Delete for current path and all parent paths 773 originalCookie.set.call(document, name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'); 774 originalCookie.set.call(document, name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=' + window.location.hostname); 775 originalCookie.set.call(document, name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.' + window.location.hostname); 776 777 // Try parent domain (e.g., .example.com for sub.example.com) 778 var domainParts = window.location.hostname.split('.'); 779 if (domainParts.length >= 2) { 780 var parentDomain = '.' + domainParts.slice(-2).join('.'); 781 originalCookie.set.call(document, name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=' + parentDomain); 782 } 783 784 // Try all path variations 785 for (var i = 0; i < pathBits.length; i++) { 786 if (pathBits[i]) { 787 pathCurrent += pathBits[i] + '/'; 788 originalCookie.set.call(document, name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + pathCurrent); 789 } 790 } 791 }, 792 793 // Clear localStorage items by pattern 794 clearLocalStorageByPattern: function(patterns) { 795 if (typeof Storage === 'undefined') return; 796 797 var keysToRemove = []; 798 799 // Find all matching keys 800 for (var i = 0; i < localStorage.length; i++) { 801 var key = localStorage.key(i); 802 803 for (var j = 0; j < patterns.length; j++) { 804 if (key && key.indexOf(patterns[j]) === 0) { 805 keysToRemove.push(key); 806 break; 807 } 808 } 809 } 810 811 // Remove the keys 812 for (var k = 0; k < keysToRemove.length; k++) { 813 try { 814 localStorage.removeItem(keysToRemove[k]); 815 } catch(e) { 816 // Ignore errors 817 } 818 } 819 820 // Also try sessionStorage 821 keysToRemove = []; 822 for (var i = 0; i < sessionStorage.length; i++) { 823 var key = sessionStorage.key(i); 824 825 for (var j = 0; j < patterns.length; j++) { 826 if (key && key.indexOf(patterns[j]) === 0) { 827 keysToRemove.push(key); 828 break; 829 } 830 } 831 } 832 833 for (var k = 0; k < keysToRemove.length; k++) { 834 try { 835 sessionStorage.removeItem(keysToRemove[k]); 836 } catch(e) { 837 // Ignore errors 838 } 839 } 840 }, 841 842 // Get current consent status 843 getConsent: getConsent, 844 845 // Show policy modal 846 showPolicy: function() { 847 var isSticky = config.displayMode === 'sticky'; 848 var policyModal = document.getElementById('shoporama-cookie-policy');
849 var overlay = document.getElementById('shoporama-cookie-overlay'); 850 851 if (policyModal) { 852 // Show overlay for modal in sticky mode 853 if (isSticky && overlay) { 854 overlay.style.display = 'block'; 855 } 856 857 policyModal.style.display = 'flex'; 858 859 // Create a click handler for the policy modal 860 var clickHandler = function(e) { 861 // Check if click is on the modal background (not on modal content) 862 if (e.target === policyModal) { 863 shoporamaCookies.hidePolicy(); 864 } 865 }; 866 867 // Add click handler to policy modal 868 policyModal.addEventListener('click', clickHandler); 869 870 // Store the handler so we can remove it later 871 policyModal._clickHandler = clickHandler; 872 873 // Also add click handler to banner container if visible 874 if (bannerContainer && bannerContainer.style.display !== 'none') { 875 bannerContainer.addEventListener('click', function(e) { 876 if (e.target === bannerContainer) { 877 shoporamaCookies.hidePolicy(); 878 } 879 }); 880 } 881 882 // Add escape key handler 883 var escapeHandler = function(e) { 884 if (e.key === 'Escape') { 885 shoporamaCookies.hidePolicy(); 886 } 887 }; 888 document.addEventListener('keydown', escapeHandler); 889 890 // Store the handler so we can remove it later 891 policyModal._escapeHandler = escapeHandler; 892 } 893 }, 894 895 // Hide policy modal 896 hidePolicy: function() { 897 var isSticky = config.displayMode === 'sticky'; 898 var policyModal = document.getElementById('shoporama-cookie-policy'); 899 var overlay = document.getElementById('shoporama-cookie-overlay'); 900 901 if (policyModal) { 902 policyModal.style.display = 'none'; 903 904 // Hide overlay in sticky mode when closing policy 905 if (isSticky && overlay) { 906 overlay.style.display = 'none'; 907 } 908 909 // Remove event listeners 910 if (policyModal._clickHandler) { 911 policyModal.removeEventListener('click', policyModal._clickHandler); 912 delete policyModal._clickHandler; 913 } 914 915 if (policyModal._escapeHandler) { 916 document.removeEventListener('keydown', policyModal._escapeHandler); 917 delete policyModal._escapeHandler; 918 } 919 } 920 }, 921 922 // Reset consent (for testing) 923 reset: function() { 924 originalCookie.set.call(document, config.consentCookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'); 925 window.location.reload(); 926 }, 927 928 // Open banner 929 openBanner: function() { 930 // Remove existing elements if any 931 var existingOverlay = document.getElementById('shoporama-cookie-overlay'); 932 var existingContainer = document.getElementById('shoporama-cookie-banner-container'); 933 var existingSettings = document.getElementById('shoporama-cookie-settings'); 934 var existingPolicy = document.getElementById('shoporama-cookie-policy'); 935 936 if (existingOverlay) existingOverlay.remove(); 937 if (existingContainer) existingContainer.remove(); 938 if (existingSettings) existingSettings.remove(); 939 if (existingPolicy) existingPolicy.remove(); 940 941 // Create new banner 942 createBanner(); 943 } 944 }; 945 946 // Create cookie settings button 947 function createSettingsButton() { 948 // Create floating button 949 var settingsBtn = document.createElement('button');
950 settingsBtn.id = 'shoporama-cookie-settings-btn'; 951 settingsBtn.innerHTML = 'ðª'; 952 settingsBtn.title = 'Cookie indstillinger'; 953 954 // Check if mobile - multiple detection methods 955 var isMobile = window.innerWidth <= 768 || 956 /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || 957 ('ontouchstart' in window); 958 959 settingsBtn.style.cssText = ` 960 position: fixed; 961 bottom: ${isMobile ? 'calc(20px + env(safe-area-inset-bottom, 0px))' : '20px'}; 962 left: ${isMobile ? '15px' : '20px'}; 963 width: ${isMobile ? '44px' : '40px'}; 964 height: ${isMobile ? '44px' : '40px'}; 965 padding: 0; 966 background: ${isMobile ? '#ffffff' : 'rgba(255, 255, 255, 0.9)'}; 967 border: 1px solid ${isMobile ? '#000000' : 'rgba(209, 213, 219, 0.5)'}; 968 border-radius: 50%; 969 color: #374151; 970 font-size: ${isMobile ? '20px' : '18px'}; 971 cursor: pointer; 972 z-index: 2147483647; 973 box-shadow: 0 2px 8px rgba(0,0,0,0.15); 974 transition: all 0.15s; 975 display: flex !important; 976 align-items: center; 977 justify-content: center; 978 opacity: ${isMobile ? '0.9' : '0.7'}; 979 -webkit-tap-highlight-color: transparent; 980 visibility: visible !important; 981 `; 982 983 settingsBtn.onmouseover = function() { 984 this.style.opacity = '1'; 985 this.style.transform = 'scale(1.05)'; 986 }; 987 988 settingsBtn.onmouseout = function() { 989 this.style.opacity = '0.7'; 990 this.style.transform = 'scale(1)'; 991 }; 992 993 var handleClick = function(e) { 994 e.preventDefault(); 995 e.stopPropagation(); 996 997 // Force create banner without consent check 998 var existingOverlay = document.getElementById('shoporama-cookie-overlay'); 999 var existingContainer = document.getElementById('shoporama-cookie-banner-container'); 1000 1001 if (existingOverlay) existingOverlay.remove(); 1002 if (existingContainer) existingContainer.remove(); 1003 1004 // Temporarily clear consent to bypass check 1005 var savedConsent = getConsent(); 1006 originalCookie.set.call(document, config.consentCookieName + '=; path=/'); 1007 1008 createBanner(); 1009 1010 // Restore consent 1011 if (savedConsent) { 1012 originalCookie.set.call(document, config.consentCookieName + '=' + 1013 encodeURIComponent(JSON.stringify(savedConsent)) + '; path=/'); 1014 } 1015 }; 1016 1017 // Add both click and touch events for better mobile support 1018 settingsBtn.addEventListener('click', handleClick); 1019 settingsBtn.addEventListener('touchstart', handleClick); 1020 1021 // Add to DOM when ready 1022 function addToDOM() { 1023 document.body.appendChild(settingsBtn); 1024 settingsBtn.style.display = 'flex'; // Ensure it's visible 1025 1026 // Extra check for mobile 1027 if (isMobile) { 1028 // Force re-apply styles in case something overwrote them 1029 setTimeout(function() { 1030 settingsBtn.style.position = 'fixed'; 1031 settingsBtn.style.bottom = '20px'; 1032 settingsBtn.style.left = '15px'; 1033 settingsBtn.style.display = 'flex'; 1034 settingsBtn.style.zIndex = '2147483647'; 1035 }, 100); 1036 } 1037 } 1038 1039 if (document.body) { 1040 addToDOM(); 1041 } else { 1042 document.addEventListener('DOMContentLoaded', addToDOM); 1043 } 1044 } 1045 1046 // Initialize consent system 1047 function init() { 1048 // CRITICAL: Set default consent states FIRST (before any tags fire) 1049 // This ensures Google tags start in 'denied' state for GDPR compliance 1050 initDefaultConsentMode(); 1051 1052 // Check for existing consent 1053 var consent = getConsent(); 1054 1055 if (!consent) { 1056 // No consent - show banner 1057 createBanner(); 1058 } else { 1059 // Apply existing consent (this will update from default 'denied' to user's actual choice) 1060 updateConsentMode(consent); 1061 // Show settings button when consent exists 1062 createSettingsButton(); 1063 } 1064 } 1065 1066 // Start when DOM is ready 1067 if (document.readyState === 'loading') { 1068 document.addEventListener('DOMContentLoaded', init); 1069 } else { 1070 init(); 1071 } 1072})();
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.