1(function (global) { 2 'use strict'; 3 4 var config = global.IntegratedConsentManagerConfig || {}; 5 var categories = Array.isArray(config.categories) && config.categories.length > 0 6 ? config.categories.slice(0) 7 : ['necessary', 'preferences', 'analytics', 'marketing']; 8 var strictOptIn = config.strictOptIn !== false; 9 var apiConfig = config.api && typeof config.api === 'object' ? config.api : {}; 10 var stateEndpoint = typeof apiConfig.stateEndpoint === 'string' && apiConfig.stateEndpoint !== '' 11 ? apiConfig.stateEndpoint 12 : '/consent/state'; 13 var decisionsEndpoint = typeof apiConfig.decisionsEndpoint === 'string' && apiConfig.decisionsEndpoint !== '' 14 ? apiConfig.decisionsEndpoint 15 : '/consent/decisions'; 16 var revokeEndpoint = typeof apiConfig.revokeEndpoint === 'string' && apiConfig.revokeEndpoint !== '' 17 ? apiConfig.revokeEndpoint 18 : '/consent/revoke'; 19 var bootstrapPolicyVersionKey = typeof config.policyVersionKey === 'string' ? config.policyVersionKey.trim() : ''; 20 var policyVersionKey = bootstrapPolicyVersionKey; 21 var userId = typeof config.userId === 'string' ? config.userId.trim() : ''; 22 var siteId = typeof config.siteId === 'string' ? config.siteId.trim() : ''; 23 var channelId = typeof config.channelId === 'string' ? config.channelId.trim() : ''; 24 var channelLabel = typeof config.channelLabel === 'string' ? config.channelLabel.trim() : ''; 25 var scopeLabel = typeof config.scopeLabel === 'string' ? config.scopeLabel.trim() : ''; 26 var bootstrapVisitorId = typeof config.visitorId === 'string' ? config.visitorId.trim() : ''; 27 var visitorStorageKey = 'integrated_consent_manager:visitor_id'; 28 var stateStorageKey = 'integrated_consent_manager:state:' + String(channelId || siteId || 'global'); 29 var startupSyncMaxAgeMs = typeof config.startupSyncMaxAgeMs === 'number' && isFinite(config.startupSyncMaxAgeMs) 30 ? Math.max(0, config.startupSyncMaxAgeMs) 31 : 10 * 60 * 1000; 32 33 var ui = { 34 banner: null, 35 bannerOverlay: null, 36 modal: null, 37 manageButton: null, 38 quickPanel: null 39 }; 40 var uiReady = config.autoSync === false || typeof global.fetch !== 'function' || typeof global.Promise !== 'function'; 41 var bannerDismissed = false; 42 var quickPanelVisible = false; 43 var modalReturnSurface = 'banner'; 44 var quickPanelPersistDebounceTimer = null; 45 var quickPanelPersistDebounceDelay = 240; 46 var language = resolveLanguage(); 47 var translations = resolveTranslations(); 48 var behaviorSettings = resolveBehaviorSettings(); 49 var designSettings = resolveDesignSettings(); 50 var brandPalette = resolveBrandPalette(); 51 var brandColor = brandPalette.primary; 52 var brandStrongColor = brandPalette.strong; 53 var privacyPolicyUrl = resolvePrivacyPolicyUrl(designSettings); 54 var categoryMetadata = resolveCategoryMetadata(); 55 var cookieDeclaration = resolveCookieDeclaration(); 56 var googleConsentModeSettings = resolveGoogleConsentModeSettings(); 57 var googleConsentModeLastSignature = ''; 58 59 function resolveLanguage() { 60 var lang = ''; 61 if (global.document && global.document.documentElement) { 62 lang = String(global.document.documentElement.getAttribute('lang') || '').trim().toLowerCase(); 63 } 64 65 if (lang.indexOf('nl') === 0) { 66 return 'nl'; 67 } 68 69 return 'en'; 70 } 71 72 function resolveTranslations() { 73 var dictionary = { 74 nl: { 75 bannerTitle: 'Deze website maakt gebruik van cookies.', 76 bannerDescription: 'We gebruiken cookies om inhoud en advertenties te personaliseren en om ons verkeer te analyseren.', 77 bannerAcceptAll: 'Alles accepteren', 78 bannerRejectOptional: 'Alles afwijzen', 79 bannerOpenPreferences: 'Details weergeven', 80 modalTitle: 'Cookieverklaring', 81 modalDescription: 'Strikt noodzakelijke cookies zijn altijd actief. Voor de overige categorieen kun je zelf kiezen.', 82 privacyPolicy: 'Privacybeleid', 83 declarationTab: 'Cookieverklaring', 84 aboutTab: 'Meer informatie over cookies', 85 aboutText: 'Cookies zijn kleine tekstbestanden die op uw computer worden geplaatst door websites die u bezoekt. Websites gebruiken cookies om gebruikers te helpen efficie
85nt te navigeren en om bepaalde functies uit te voeren. Cookies die nodig zijn om de website goed te laten functioneren, mogen zonder uw toestemming worden geplaatst. Alle andere cookies moeten worden goedgekeurd voordat ze in de browser kunnen worden ingesteld. U kunt uw toestemming voor het gebruik van cookies op elk moment wijzigen op onze pagina Privacybeleid.', 86 savePreferences: 'Opslaan en sluiten', 87 showCookies: 'Toon cookies', 88 cookieName: 'Naam', 89 cookieProviderDomain: 'Aanbieder / Domein', 90 cookieExpiry: 'Vervaldatum', 91 cookieDescription: 'Omschrijving', 92 noCookiesDetected: 'Nog geen cookie-details beschikbaar voor deze categorie.', 93 manageButton: 'Cookie-instellingen', 94 savingDecision: 'Voorkeuren worden opgeslagen...' 95 }, 96 en: { 97 bannerTitle: 'This website uses cookies.', 98 bannerDescription: 'We use cookies to personalize content and ads and to analyze traffic.', 99 bannerAcceptAll: 'Accept all', 100 bannerRejectOptional: 'Reject optional', 101 bannerOpenPreferences: 'Show details', 102 modalTitle: 'Cookie declaration', 103 modalDescription: 'Strictly necessary cookies are always active. You can choose the optional categories.', 104 privacyPolicy: 'Privacy policy', 105 declarationTab: 'Cookie declaration', 106 aboutTab: 'More about cookies', 107 aboutText: 'Cookies are small text files used by websites to remember preferences, build statistics, and enable features.', 108 savePreferences: 'Save and close', 109 showCookies: 'Show cookies', 110 cookieName: 'Name', 111 cookieProviderDomain: 'Provider / Domain', 112 cookieExpiry: 'Expiry', 113 cookieDescription: 'Description', 114 noCookiesDetected: 'No cookie details are available yet for this category.', 115 manageButton: 'Cookie settings', 116 savingDecision: 'Saving preferences...' 117 } 118 }; 119 120 var defaults = dictionary[language] || dictionary.en; 121 var configured = config.translations && typeof config.translations === 'object' 122 ? config.translations 123 : null; 124 125 if (!configured) { 126 return defaults; 127 } 128 129 var languageOverrides = configured; 130 if (configured[language] && typeof configured[language] === 'object') { 131 languageOverrides = configured[language]; 132 } else if (configured.en && typeof configured.en === 'object') { 133 languageOverrides = configured.en; 134 } 135 136 var resolved = {}; 137 for (var key in defaults) { 138 if (!Object.prototype.hasOwnProperty.call(defaults, key)) { 139 continue; 140 } 141 resolved[key] = defaults[key]; 142 } 143 144 if (languageOverrides && typeof languageOverrides === 'object') { 145 for (var overrideKey in languageOverrides) { 146 if (!Object.prototype.hasOwnProperty.call(languageOverrides, overrideKey)) { 147 continue; 148 } 149 var overrideValue = languageOverrides[overrideKey]; 150 if (typeof overrideValue === 'string' && overrideValue.trim() !== '') { 151 resolved[overrideKey] = overrideValue.trim(); 152 } 153 } 154 } 155 156 return resolved; 157 } 158 159 function resolveBoolean(value, fallback) { 160 if (typeof value === 'boolean') { 161 return value; 162 } 163 164 return fallback; 165 } 166 167 function resolveBehaviorSettings() { 168 var defaults = { 169 reloadPageOnConsentChange: true, 170 focusTrapEnabled: true 171 }; 172 173 var behavior = config.behavior && typeof config.behavior === 'object' ? config.behavior : {}; 174 defaults.reloadPageOnConsentChange = resolveBoolean(behavior.reloadPageOnConsentChange, defaults.reloadPageO
174nConsentChange); 175 defaults.focusTrapEnabled = resolveBoolean(behavior.focusTrapEnabled, defaults.focusTrapEnabled); 176 177 if (typeof config.reloadPageOnConsentChange === 'boolean') { 178 defaults.reloadPageOnConsentChange = config.reloadPageOnConsentChange; 179 } 180 if (typeof config.focusTrapEnabled === 'boolean') { 181 defaults.focusTrapEnabled = config.focusTrapEnabled; 182 } 183 184 return defaults; 185 } 186 187 function resolveDesignSettings() { 188 var defaults = { 189 bannerStyle: 'side_banner', 190 themeVariant: 'dark', 191 bannerVerticalPosition: 'bottom', 192 bannerHorizontalPosition: 'left', 193 bannerCornerStyle: 'semi_round', 194 fullPageOverlay: false, 195 showCookieLogo: true, 196 showCloseIcon: true, 197 showPrivacyPolicyLink: true, 198 privacyPolicyUrl: '/privacy-policy', 199 privacyPolicyLinkTarget: 'same_window' 200 }; 201 202 var design = config.design && typeof config.design === 'object' ? config.design : {}; 203 204 if (typeof design.bannerStyle === 'string') { 205 var style = design.bannerStyle.trim(); 206 if (style === 'side_banner' || style === 'full_width_big' || style === 'full_width_small' || style === 'centered') { 207 defaults.bannerStyle = style; 208 } 209 } 210 211 if (typeof design.themeVariant === 'string') { 212 var themeVariant = design.themeVariant.trim(); 213 if (themeVariant === 'dark' || themeVariant === 'light') { 214 defaults.themeVariant = themeVariant; 215 } 216 } 217 218 if (typeof design.bannerVerticalPosition === 'string') { 219 var vertical = design.bannerVerticalPosition.trim(); 220 if (vertical === 'top' || vertical === 'bottom' || vertical === 'center') { 221 defaults.bannerVerticalPosition = vertical; 222 } 223 } 224 225 if (typeof design.bannerHorizontalPosition === 'string') { 226 var horizontal = design.bannerHorizontalPosition.trim(); 227 if (horizontal === 'left' || horizontal === 'right') { 228 defaults.bannerHorizontalPosition = horizontal; 229 } 230 } 231 232 if (typeof design.bannerCornerStyle === 'string') { 233 var corner = design.bannerCornerStyle.trim(); 234 if (corner === 'round' || corner === 'semi_round' || corner === 'square') { 235 defaults.bannerCornerStyle = corner; 236 } 237 } 238 239 defaults.fullPageOverlay = resolveBoolean(design.fullPageOverlay, defaults.fullPageOverlay); 240 defaults.showCookieLogo = resolveBoolean(design.showCookieLogo, defaults.showCookieLogo); 241 defaults.showCloseIcon = resolveBoolean(design.showCloseIcon, defaults.showCloseIcon); 242 defaults.showPrivacyPolicyLink = resolveBoolean(design.showPrivacyPolicyLink, defaults.showPrivacyPolicyLink); 243 244 if (typeof design.privacyPolicyUrl === 'string' && design.privacyPolicyUrl.trim() !== '') { 245 defaults.privacyPolicyUrl = design.privacyPolicyUrl.trim(); 246 } 247 248 if (typeof design.privacyPolicyLinkTarget === 'string') { 249 var linkTarget = design.privacyPolicyLinkTarget.trim(); 250 if (linkTarget === 'same_window' || linkTarget === 'new_window') { 251 defaults.privacyPolicyLinkTarget = linkTarget; 252 } 253 } 254 255 if (typeof config.privacyPolicyUrl === 'string' && config.privacyPolicyUrl.trim() !== '') { 256 defaults.privacyPolicyUrl = config.privacyPolicyUrl.trim(); 257 } 258 259 if (typeof config.privacyPolicyLinkTarget === 'string') { 260 var globalTarget = config.privacyPolicyLinkTarget.trim(); 261 if (globalTarget === 'same_window' || globalTarget === 'new_window') { 262 defaults.privacyPolicyLinkTarget = globalTarget; 263 } 264 } 265 266 if (typeof config.showPrivacyPolicyLink === 'boolean') { 267 defaults.showPrivacyPolicyLink = config.showPrivacyPolicyLink; 268 } 269 270 return defaults; 271 } 272 273 function resolveGoogleConsentModeSettings() { 274 var defaults = { 275 enabled: false, 276 autoDetectIssues: false, 277 adsCategory: 'marketing', 278 adUserDataCategory: 'marketing', 279 adPersonalizationCategory: 'marketing', 280 analyticsCategory: 'analytics', 281 functionalityCategory: 'preferences', 282 personalizationCategory: 'preferences', 283 securityCategory: 'preferences' 284 }; 285 286 var configured = config.googleConsentMode && typeof config.googleConsentMode === 'object' 287 ? config.googleConsentMode 288 : {}; 289 290 defaults.enabled = resolveBoolean(configured.enabled, defaults.enabled); 291 defaults.autoDetectIssues = resolveBoolean(configured.autoDetectIssues, defaults.autoDetectIssues); 292 293 defaults.adsCategory = normalizeGoogleConsentModeCategory(configured.adsCategory, defaults.adsCategory); 294 defaults.adUserDataCategory = normalizeGoogleConsentModeCategory(configured.adUserDataCategory, defaults.adUserDataCategory); 295 defaults.adPersonalizationCategory = normalizeGoogleConsentModeCategory(configured.adPersonalizationCategory, defaults.adPersonalizationCategory); 296 defaults.analyticsCategory = normalizeGoogleConsentModeCategory(configured.analyticsCategory, defaults.analyticsCategory); 297 defaults.functionalityCategory = normalizeGoogleConsentModeCategory(configured.functionalityCategory, defaults.functionalityCategory); 298 defaults.personalizationCategory = normalizeGoogleConsentModeCategory(configured.personalizationCategory, defaults.personalizationCategory); 299 defaults.securityCategory = normalizeGoogleConsentModeCategory(configured.securityCategory, defaults.securityCategory); 300 301 return defaults; 302 } 303 304 function resolveBrandPalette() { 305 var fallbackPrimary = '#2f7f2c'; 306 var fallbackStrong = '#1f2937'; 307 if (!global.document || !global.document.documentElement || typeof global.getComputedStyle !== 'function') { 308 return { 309 primary: fallbackPrimary, 310 strong: fallbackStrong 311 }; 312 } 313 314 var rootStyle = global.getComputedStyle(global.document.documentElement); 315 var bodyStyle = global.document.body ? global.getComputedStyle(global.document.body) : null; 316
317 var primaryCandidate = resolveCssPropertyCandidate( 318 ['--color-primary', '--dashboard-accent', '--brand-primary', '--primary-color'], 319 rootStyle, 320 bodyStyle 321 ); 322 if (primaryCandidate === '') { 323 primaryCandidate = resolveMetaThemeColor(); 324 } 325 var primary = normalizeCssColor(primaryCandidate); 326 if (primary === '') { 327 primary = fallbackPrimary; 328 } 329 330 var secondaryCandidate = resolveCssPropertyCandidate( 331 ['--color-secondary', '--dashboard-accent-soft', '--brand-secondary', '--secondary-color'], 332 rootStyle, 333 bodyStyle 334 ); 335 var strong = normalizeCssColor(secondaryCandidate); 336 if (strong === '') { 337 strong = deriveStrongBrandColor(primary); 338 } 339 if (strong === '') { 340 strong = fallbackStrong; 341 } 342 343 return { 344 primary: primary, 345 strong: strong 346 }; 347 } 348 349 function resolveCssPropertyCandidate(variables, rootStyle, bodyStyle) { 350 for (var i = 0; i < variables.length; i++) { 351 var variable = variables[i]; 352 var rootValue = rootStyle ? String(rootStyle.getPropertyValue(variable) || '').trim() : ''; 353 if (rootValue !== '') { 354 return rootValue; 355 } 356 357 var bodyValue = bodyStyle ? String(bodyStyle.getPropertyValue(variable) || '').trim() : ''; 358 if (bodyValue !== '') { 359 return bodyValue; 360 } 361 } 362 363 return ''; 364 } 365 366 function resolveMetaThemeColor() { 367 if (!global.document || typeof global.document.querySelector !== 'function') { 368 return ''; 369 } 370 371 var meta = global.document.querySelector('meta[name="theme-color"], meta[name="msapplication-TileColor"]'); 372 if (!meta) { 373 return ''; 374 } 375 376 return String(meta.getAttribute('content') || '').trim(); 377 } 378 379 function normalizeCssColor(candidate) { 380 var value = typeof candidate === 'string' ? candidate.trim() : ''; 381 if (value === '') { 382 return ''; 383 } 384 385 if (!global.document || typeof global.document.createElement !== 'function') { 386 return value; 387 } 388 389 var probe = global.document.createElement('span'); 390 probe.style.color = ''; 391 probe.style.color = value; 392 if (!probe.style.color) { 393 return ''; 394 } 395 396 return value; 397 } 398 399 function deriveStrongBrandColor(baseColor) { 400 var rgb = parseColorToRgb(baseColor); 401 if (!rgb) { 402 return ''; 403 } 404 405 var red = Math.max(0, Math.min(255, Math.round(rgb.r * 0.58))); 406 var green = Math.max(0, Math.min(255, Math.round(rgb.g * 0.58))); 407 var blue = Math.max(0, Math.min(255, Math.round(rgb.b * 0.58))); 408 409 return 'rgb(' + red + ', ' + green + ', ' + blue + ')'; 410 } 411 412 function parseColorToRgb(value) { 413 var color = typeof value === 'string' ? value.trim() : ''; 414 if (color === '') { 415 return null; 416 } 417 418 if (color.charAt(0) === '#') { 419 return parseHexColor(color); 420 } 421 422 var rgbMatch = color.match(/^rgba?\(([^)]+)\)$/i); 423 if (rgbMatch) { 424 var parts = rgbMatch[1].split(','); 425 if (parts.length >= 3) { 426 var r = parseInt(parts[0], 10); 427 var g = parseInt(parts[1], 10); 428 var b = parseInt(parts[2], 10); 429 if (!isNaN(r) && !isNaN(g) && !isNaN(b)) { 430 return { r: r, g: g, b: b }; 431 } 432 } 433 } 434 435 if (!global.document || typeof global.document.createElement !== 'function') { 436 return null; 437 } 438 439 var probe = global.document.createElement('span'); 440 probe.style.color = color; 441 if (!probe.style.color) { 442 return null; 443 } 444 445 return parseColorToRgb(probe.style.color); 446 } 447 448 function parseHexColor(color) { 449 var value = color.replace('#', ''); 450 if (value.length === 3) { 451 value = value.charAt(0) + value.charAt(0) 452 + value.charAt(1) + value.charAt(1) 453 + value.charAt(2) + value.charAt(2); 454 } 455 456 if (value.length !== 6) { 457 return null; 458 } 459 460 var intValue = parseInt(value, 16); 461 if (isNaN(intValue)) { 462 return null; 463 } 464 465 return { 466 r: (intValue >> 16) & 255, 467 g: (intValue >> 8) & 255, 468 b: intValue & 255 469 }; 470 } 471 472 function resolvePrivacyPolicyUrl(design) { 473 if (design && typeof design.privacyPolicyUrl === 'string' && design.privacyPolicyUrl.trim() !== '') { 474 return design.privacyPolicyUrl.trim(); 475 } 476 477 return '/privacy-policy'; 478 } 479
480 function resolveCategoryMetadata() { 481 var defaults = { 482 necessary: { 483 label: language === 'nl' ? 'Strikt noodzakelijk' : 'Strictly necessary', 484 description: language === 'nl' 485 ? 'Deze cookies zijn nodig om de website goed te laten werken en kunnen niet worden uitgeschakeld.' 486 : 'These cookies are required for the website to function and cannot be disabled.' 487 }, 488 preferences: { 489 label: language === 'nl' ? 'Functioneel' : 'Functional', 490 description: language === 'nl' 491 ? 'Functionele cookies onthouden voorkeuren zoals taal en presentatie-instellingen.' 492 : 'Functional cookies remember preferences such as language and presentation settings.' 493 }, 494 analytics: { 495 label: language === 'nl' ? 'Prestatie' : 'Performance', 496 description: language === 'nl' 497 ? 'Prestatiecookies helpen ons begrijpen hoe bezoekers de site gebruiken.' 498 : 'Performance cookies help us understand how visitors use the site.' 499 }, 500 marketing: { 501 label: language === 'nl' ? 'Targeting' : 'Targeting', 502 description: language === 'nl' 503 ? 'Targeting cookies worden gebruikt om relevante content en advertenties te tonen.' 504 : 'Targeting cookies are used to show relevant content and ads.' 505 } 506 }; 507 508 var configured = config.categoryMetadata && typeof config.categoryMetadata === 'object' 509 ? config.categoryMetadata 510 : {}; 511 512 for (var key in configured) { 513 if (!Object.prototype.hasOwnProperty.call(configured, key)) { 514 continue; 515 } 516 517 if (!defaults[key]) { 518 defaults[key] = { 519 label: key, 520 description: '' 521 }; 522 } 523 524 var row = configured[key]; 525 if (row && typeof row === 'object') { 526 if (typeof row.label === 'string' && row.label.trim() !== '') { 527 defaults[key].label = row.label.trim(); 528 } 529 if (typeof row.description === 'string' && row.description.trim() !== '') { 530 defaults[key].description = row.description.trim(); 531 } 532 } 533 } 534 535 return defaults; 536 } 537 538 function resolveCookieDeclaration() { 539 var defaults = { 540 necessary: [ 541 { 542 name: 'PHPSESSID', 543 provider: 'PHP.net', 544 domain: language === 'nl' ? 'huidige website' : 'current website', 545 expiry: language === 'nl' ? 'Sessie' : 'Session', 546 description: language === 'nl' 547 ? 'Sessiecookie om basisfunctionaliteit van de website mogelijk te maken.' 548 : 'Session cookie required for core website functionality.' 549 } 550 ], 551 preferences: [], 552 analytics: [ 553 { 554 name: '_ga', 555 provider: 'Google LLC', 556 domain: '.google-analytics.com', 557 expiry: language === 'nl' ? '1 jaar 1 maand' : '1 year 1 month', 558 description: language === 'nl' 559 ? 'Wordt gebruikt om bezoekers en sessies te onderscheiden voor statistieken.' 560 : 'Used to distinguish visitors and sessions for analytics.' 561 } 562 ], 563 marketing: [ 564 { 565 name: '_fbp', 566 provider: 'Meta Platforms, Inc.', 567 domain: '.facebook.com', 568 expiry: language === 'nl' ? '3 maanden' : '3 months', 569 description: language === 'nl' 570 ? 'Wordt gebruikt om advertentiemetingen en personalisatie mogelijk te maken.' 571 : 'Used for ad measurement and personalization.' 572 } 573 ] 574 }; 575 576 var configured = config.cookieDeclaration && typeof config.cookieDeclaration === 'object' 577 ? config.cookieDeclaration 578 : {}; 579 580 for (var key in configured) { 581 if (!Object.prototype.hasOwnProperty.call(configured, key)) { 582 continue; 583 } 584 585 if (!Array.isArray(configured[key])) { 586 continue; 587 } 588 589 defaults[key] = configured[key]; 590 } 591 592 return defaults; 593 } 594 595 function normalizeCategoryKey(category) { 596 return String(category || '') 597 .trim() 598 .toLowerCase() 599 .replace(/[\s-]+/g, '_'); 600 } 601 602 function normalizeGoogleConsentModeCategory(category, fallback) { 603 var normalized = normalizeCategoryKey(category); 604 var aliases = { 605 targeting: 'marketing', 606 performance: 'analytics', 607 functionality: 'preferences',
608 strict: 'necessary', 609 strict_necessary: 'necessary', 610 strictly_necessary: 'necessary', 611 do_not_block: 'ignore' 612 }; 613 614 if (Object.prototype.hasOwnProperty.call(aliases, normalized)) { 615 normalized = aliases[normalized]; 616 } 617 618 if ( 619 normalized === 'necessary' 620 || normalized === 'preferences' 621 || normalized === 'analytics' 622 || normalized === 'marketing' 623 || normalized === 'unclassified' 624 || normalized === 'ignore' 625 ) { 626 return normalized; 627 } 628 629 return typeof fallback === 'string' && fallback !== '' ? fallback : 'marketing'; 630 } 631 632 function isAlwaysOnCategory(category) { 633 var key = normalizeCategoryKey(category); 634 return key === 'necessary' 635 || key === 'strict' 636 || key === 'strictly_necessary' 637 || key === 'strict_necessary' 638 || key === 'essential' 639 || key === 'required'; 640 } 641 642 function getOptionalCategories() { 643 var optional = []; 644 for (var i = 0; i < categories.length; i++) { 645 if (!isAlwaysOnCategory(categories[i])) { 646 optional.push(categories[i]); 647 } 648 } 649 650 return optional; 651 } 652 653 function getCategoryDisplayOrder() { 654 var alwaysOn = []; 655 var optional = []; 656 for (var i = 0; i < categories.length; i++) { 657 if (isAlwaysOnCategory(categories[i])) { 658 alwaysOn.push(categories[i]); 659 } else { 660 optional.push(categories[i]); 661 } 662 } 663 664 return alwaysOn.concat(optional); 665 } 666 667 function enforceAlwaysOnCategories(state) { 668 if (!state || typeof state !== 'object') { 669 return state; 670 } 671 672 for (var category in state) { 673 if (!Object.prototype.hasOwnProperty.call(state, category)) { 674 continue; 675 } 676 if (isAlwaysOnCategory(category)) { 677 state[category] = true; 678 } 679 } 680 681 return state; 682 } 683 684 function createBaselineState() { 685 var state = {}; 686 for (var i = 0; i < categories.length; i++) { 687 state[categories[i]] = !strictOptIn; 688 } 689 690 return enforceAlwaysOnCategories(state); 691 } 692 693 function buildDefaultState() { 694 if (config.defaultState && typeof config.defaultState === 'object') { 695 return normalizeCategories(config.defaultState); 696 } 697 698 return createBaselineState(); 699 } 700 701 function buildAcceptAllState() { 702 var accepted = {}; 703 for (var i = 0; i < categories.length; i++) { 704 accepted[categories[i]] = true; 705 } 706 707 return enforceAlwaysOnCategories(accepted); 708 } 709 710 function buildRejectOptionalState() { 711 var rejected = {}; 712 for (var i = 0; i < categories.length; i++) { 713 rejected[categories[i]] = false; 714 } 715 716 return enforceAlwaysOnCategories(rejected); 717 } 718 719 function resolveGoogleConsentSignal(category, categoriesState) { 720 var normalizedCategory = normalizeGoogleConsentModeCategory(category, ''); 721 if (normalizedCategory === 'ignore') { 722 return null; 723 } 724 if (normalizedCategory === 'necessary') { 725 return 'granted'; 726 } 727 if (normalizedCategory === 'unclassified') { 728 return 'denied'; 729 } 730 731 return categoriesState && categoriesState[normalizedCategory] === true ? 'granted' : 'denied'; 732 } 733 734 function applyGoogleConsentModeUpdate(categoriesState, source) { 735 if (!googleConsentModeSettings.enabled) { 736 return; 737 } 738 739 var signals = {}; 740 var mappings = { 741 ad_storage: googleConsentModeSettings.adsCategory, 742 analytics_storage: googleConsentModeSettings.analyticsCategory, 743 ad_user_data: googleConsentModeSettings.adUserDataCategory, 744 ad_personalization: googleConsentModeSettings.adPersonalizationCategory, 745 functionality_storage: googleConsentModeSettings.functionalityCategory, 746 personalization_storage: googleConsentModeSettings.personalizationCategory, 747 security_storage: googleConsentModeSettings.securityCategory 748 }; 749 750 for (var key in mappings) { 751 if (!Object.prototype.hasOwnProperty.call(mappings, key)) { 752 continue; 753 } 754 755 var signal = resolveGoogleConsentSignal(mappings[key], categoriesState); 756 if (signal === null) { 757 continue; 758 } 759 signals[key] = signal; 760 } 761 762 if (Object.keys(signals).length === 0) { 763 return; 764 } 765 766 var signature = JSON.stringify(signals); 767 if (signature === googleConsentModeLastSignature) { 768 return; 769 } 770 googleConsentModeLastSignature = signature; 771 772 if (typeof global.gtag === 'function') { 773 try { 774 global.gtag('consent', 'update', signals); 775 } catch (error) { 776 return; 777 } 778 } 779 780 if (!global.dataLayer || typeof global.dataLayer.push !== 'function') { 781 return; 782 } 783 784 global.dataLayer.push({ 785 event: 'icm_google_consent_update', 786 source: String(source || 'runtime:apply_state'), 787 signals: signals 788 }); 789 } 790 791 function randomToken(size) { 792 var alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; 793 var token = ''; 794 var length = typeof size === 'number' && size > 0 ? size : 12; 795 796 for (var i = 0; i < length; i++) { 797 token += alphabet.charAt(Math.floor(Math.random() * alphabet.length)); 798 } 799 800 return token; 801 } 802 803 function generateVisitorId() { 804 return 'icm-' + Date.now().toString(36) + '-' + randomToken(10); 805 } 806 807 function resolveVisitorId() { 808 if (bootstrapVisitorId !== '') { 809 return bootstrapVisitorId; 810 } 811 812 if (!global.localStorage) { 813 return generateVisitorId(); 814 } 815 816 try { 817 var existing = global.localStorage.getItem(visitorStorageKey); 818 if (typeof existing === 'string' && existing.trim() !== '') { 819 return existing.trim(); 820 } 821 822 var generated = generateVisitorId(); 823 global.localStorage.setItem(visitorStorageKey, generated); 824 825 return generated; 826 } catch (error) { 827 return generateVisitorId(); 828 } 829 } 830 831 var visitorId = resolveVisitorId(); 832 833 function notifyUpdate(source) { 834 var detail = { 835 source: String(source || 'runtime:update'), 836 categories: currentState.categories, 837 persistedDecision: currentState.persistedDecision, 838 pendingRequest: currentState.pendingRequest 839 }; 840 841 if (typeof global.CustomEvent === 'function') { 842 global.dispatchEvent(new CustomEvent('IntegratedConsentUpdated', {detail: detail})); 843 } 844 } 845 846 function normalizeCategories(input) { 847 var next = createBaselineState(); 848 if (!input || typeof input !== 'object') { 849 return next; 850 } 851 852 for (var i = 0; i < categories.length; i++) { 853 var key = categories[i]; 854 if (typeof input[key] === 'boolean') { 855 next[key] = input[key]; 856 } 857 } 858 859 return enforceAlwaysOnCategories(next); 860 } 861 862 function getStateSnapshot() { 863 return { 864 categories: currentState.categories, 865 persistedDecision: currentState.persistedDecision, 866 pendingRequest: currentState.pendingRequest, 867 policyVersionKey: policyVersionKey, 868 visitorId: visitorId, 869 siteId: siteId, 870 channelId: channelId, 871 channelLabel: channelLabel, 872 scopeLabel: resolveScopeLabel() 873 }; 874 } 875 876 function readPersistedStateSnapshot() { 877 if (!global.localStorage) { 878 return null; 879 } 880 881 try { 882 var raw = global.localStorage.getItem(stateStorageKey); 883 if (typeof raw !== 'string' || raw.trim() === '') { 884 return null; 885 } 886 887 var parsed = JSON.parse(raw); 888 if (!parsed || typeof parsed !== 'object') { 889 return null; 890 } 891 892 return parsed; 893 } catch (error) { 894 return null; 895 } 896 } 897 898 function persistStateSnapshot(markVerified) { 899 if (!global.localStorage) { 900 return; 901 } 902 903 try { 904 var timestamp = Date.now(); 905 global.localStorage.setItem(stateStorageKey, JSON.stringify({ 906 categories: currentState.categories, 907 persistedDecision: currentState.persistedDecision, 908 policyVersionKey: policyVersionKey, 909 visitorId: visitorId, 910 siteId: siteId, 911 channelId: channelId, 912 updatedAt: timestamp, 913 verifiedAt: markVerified === true ? timestamp : null 914 })); 915 } catch (error) { 916 } 917 } 918 919 function normalizeSnapshotTimestamp(value) { 920 if (typeof value === 'number' && isFinite(value) && value > 0) { 921 return value; 922 } 923 924 if (typeof value === 'string') { 925 var parsed = parseInt(value, 10); 926 if (isFinite(parsed) && parsed > 0) { 927 return parsed; 928 } 929 } 930 931 return 0; 932 } 933 934 var currentState = { 935 categories: buildDefaultState(), 936 persistedDecision: false, 937 pendingRequest: false 938 }; 939
940 function applySnapshotToRuntimeState(stored, source) { 941 if (!stored || typeof stored !== 'object') { 942 return; 943 } 944 945 if ( 946 typeof stored.policyVersionKey === 'string' 947 && stored.policyVersionKey.trim() !== '' 948 && bootstrapPolicyVersionKey === '' 949 && policyVersionKey === '' 950 ) { 951 policyVersionKey = stored.policyVersionKey.trim(); 952 } 953 if ( 954 typeof stored.visitorId === 'string' 955 && stored.visitorId.trim() !== '' 956 && bootstrapVisitorId === '' 957 ) { 958 visitorId = stored.visitorId.trim(); 959 } 960 961 currentState.categories = normalizeCategories(stored.categories || null); 962 currentState.persistedDecision = stored.persistedDecision === true; 963 applyGoogleConsentModeUpdate(currentState.categories, source || 'runtime:storage_hydrate'); 964 } 965 966 function hydrateStateFromStorage() { 967 var stored = readPersistedStateSnapshot(); 968 if (!stored) { 969 return null; 970 } 971 972 applySnapshotToRuntimeState(stored, 'runtime:storage_hydrate'); 973 974 return stored; 975 } 976 977 function applyState(nextCategories, persistedDecision, source, options) { 978 var opts = options && typeof options === 'object' ? options : {}; 979 var previousCategories = currentState.categories; 980 currentState.categories = normalizeCategories(nextCategories); 981 currentState.persistedDecision = persistedDecision === true; 982 applyGoogleConsentModeUpdate(currentState.categories, source || 'runtime:apply_state'); 983 deactivateRevokedCategories(previousCategories, currentState.categories); 984 if (opts.persistSnapshot !== false) { 985 persistStateSnapshot(opts.markVerified === true); 986 } 987 activateBlockedScriptsForGrantedCategories(); 988 notifyUpdate(source || 'runtime:apply_state'); 989 renderConsentUi(); 990 991 return getStateSnapshot(); 992 } 993 994 function buildPromiseRejection(message) { 995 if (typeof global.Promise !== 'function') { 996 return null; 997 } 998 999 return global.Promise.reject(new Error(message)); 1000 } 1001 1002 function fetchJson(url, extraHeaders) { 1003 if (typeof global.fetch !== 'function' || typeof global.Promise !== 'function') { 1004 return buildPromiseRejection('fetch_not_available'); 1005 } 1006 1007 var headers = { 1008 Accept: 'application/json' 1009 }; 1010 if (extraHeaders && typeof extraHeaders === 'object') { 1011 for (var headerName in extraHeaders) { 1012 if (!Object.prototype.hasOwnProperty.call(extraHeaders, headerName)) { 1013 continue; 1014 } 1015 1016 var headerValue = extraHeaders[headerName]; 1017 if (typeof headerValue !== 'string' || headerValue.trim() === '') { 1018 continue; 1019 } 1020 1021 headers[headerName] = headerValue; 1022 } 1023 } 1024 1025 return global.fetch(url, { 1026 method: 'GET', 1027 credentials: 'same-origin', 1028 headers: headers 1029 }).then(function (response) { 1030 if (!response.ok) { 1031 throw new Error('http_' + response.status); 1032 } 1033 1034 return response.json(); 1035 }); 1036 } 1037 1038 function postJson(url, payload) { 1039 if (typeof global.fetch !== 'function' || typeof global.Promise !== 'function') { 1040 return buildPromiseRejection('fetch_not_available'); 1041 } 1042 1043 return global.fetch(url, { 1044 method: 'POST', 1045 credentials: 'same-origin', 1046 headers: { 1047 'Content-Type': 'application/json', 1048 Accept: 'application/json' 1049 }, 1050 body: JSON.stringify(payload || {}) 1051 }).then(function (response) { 1052 return response.json().catch(function () { 1053 return {}; 1054 }).then(function (responsePayload) { 1055 if (!response.ok) { 1056 var error = new Error('http_' + response.status); 1057 error.status = response.status; 1058 error.payload = responsePayload && typeof responsePayload === 'object' ? responsePayload : {}; 1059 1060 throw error; 1061 } 1062 1063 return responsePayload; 1064 }); 1065 }); 1066 } 1067 1068 function buildDecisionPayload(nextCategories, source) { 1069 var payload = { 1070 visitor_id: visitorId, 1071 categories: normalizeCategories(nextCategories), 1072 source: String(source || 'runtime:persist_decision') 1073 }; 1074 if (policyVersionKey !== '') { 1075 payload.policy_version_key = policyVersionKey; 1076 } 1077 1078 if (userId !== '') { 1079 payload.user_id = userId; 1080 } 1081 if (siteId !== '') { 1082 payload.site_id = siteId; 1083 } 1084 if (channelId !== '') { 1085 payload.channel_id = channelId; 1086 } 1087 1088 return payload; 1089 } 1090 1091 function buildRevokePayload() { 1092 var payload = { 1093 visitor_id: visitorId 1094 }; 1095 if (policyVersionKey !== '') { 1096 payload.policy_version_key = policyVersionKey; 1097 } 1098 1099 if (userId !== '') { 1100 payload.user_id = userId; 1101 } 1102 if (siteId !== '') { 1103 payload.site_id = siteId; 1104 } 1105 if (channelId !== '') { 1106 payload.channel_id = channelId; 1107 } 1108 1109 return payload; 1110 } 1111 1112 function buildStateRequestHeaders() { 1113 return {}; 1114 } 1115 1116 function snapshotMatchesCurrentScope(stored) { 1117 if (!stored || typeof stored !== 'object') { 1118 return false;
1119 } 1120 1121 var storedSiteId = typeof stored.siteId === 'string' ? stored.siteId.trim() : ''; 1122 var storedChannelId = typeof stored.channelId === 'string' ? stored.channelId.trim() : ''; 1123 if (siteId !== '' && storedSiteId !== '' && storedSiteId !== siteId) { 1124 return false; 1125 } 1126 if (channelId !== '' && storedChannelId !== '' && storedChannelId !== channelId) { 1127 return false; 1128 } 1129 1130 return true; 1131 } 1132 1133 function shouldSyncStateOnStartup(stored) { 1134 if (!stored || typeof stored !== 'object') { 1135 return true; 1136 } 1137 1138 if (!snapshotMatchesCurrentScope(stored)) { 1139 return true; 1140 } 1141 1142 if (bootstrapVisitorId === '') { 1143 return true; 1144 } 1145 1146 var storedVisitorId = typeof stored.visitorId === 'string' ? stored.visitorId.trim() : ''; 1147 if (storedVisitorId === '' || storedVisitorId !== bootstrapVisitorId) { 1148 return true; 1149 } 1150 1151 if (bootstrapPolicyVersionKey !== '') { 1152 var storedPolicyVersionKey = typeof stored.policyVersionKey === 'string' ? stored.policyVersionKey.trim() : ''; 1153 if (storedPolicyVersionKey === '' || storedPolicyVersionKey !== bootstrapPolicyVersionKey) { 1154 return true; 1155 } 1156 } 1157 1158 var verifiedTimestamp = normalizeSnapshotTimestamp(stored.verifiedAt); 1159 if (verifiedTimestamp <= 0 && !Object.prototype.hasOwnProperty.call(stored, 'verifiedAt')) { 1160 verifiedTimestamp = normalizeSnapshotTimestamp(stored.updatedAt); 1161 } 1162 1163 if (verifiedTimestamp <= 0) { 1164 return true; 1165 } 1166 1167 return Date.now() - verifiedTimestamp > startupSyncMaxAgeMs; 1168 } 1169 1170 function syncStateFromStoredSnapshot(source) { 1171 var stored = readPersistedStateSnapshot(); 1172 if (!stored || !snapshotMatchesCurrentScope(stored)) { 1173 return getStateSnapshot(); 1174 } 1175 1176 applySnapshotToRuntimeState(stored, source || 'runtime:storage_sync'); 1177 activateBlockedScriptsForGrantedCategories(); 1178 notifyUpdate(source || 'runtime:storage_sync'); 1179 renderConsentUi(); 1180 1181 return getStateSnapshot(); 1182 } 1183 1184 function registerStorageSyncListener() { 1185 if (!global || typeof global.addEventListener !== 'function') { 1186 return; 1187 } 1188 1189 global.addEventListener('storage', function (event) { 1190 if (!event || event.key !== stateStorageKey) { 1191 return; 1192 } 1193 1194 syncStateFromStoredSnapshot('runtime:storage_sync'); 1195 }); 1196 } 1197 1198 function isRecoverableVisitorContextError(error) { 1199 if (!error || error.status !== 409) { 1200 return false; 1201 } 1202 1203 var payload = error.payload && typeof error.payload === 'object' ? error.payload : {}; 1204 var errorCode = typeof payload.error === 'string' ? payload.error : ''; 1205 1206 return errorCode === 'missing_visitor_cookie' || errorCode === 'invalid_visitor_context'; 1207 } 1208 1209 function syncStateFromApi() { 1210 return fetchJson(stateEndpoint, buildStateRequestHeaders()).then(function (payload) { 1211 if (!payload || typeof payload !== 'object') { 1212 return getStateSnapshot(); 1213 } 1214 1215 if (typeof payload.policy_version_key === 'string' && payload.policy_version_key.trim() !== '') { 1216 policyVersionKey = payload.policy_version_key.trim(); 1217 } 1218 if (typeof payload.visitor_id === 'string' && payload.visitor_id.trim() !== '') { 1219 visitorId = payload.visitor_id.trim(); 1220 } 1221 if (typeof payload.site_id === 'string' && payload.site_id.trim() !== '' && siteId === '') { 1222 siteId = payload.site_id.trim(); 1223 } 1224 if (typeof payload.channel_id === 'string' && payload.channel_id.trim() !== '' && channelId === '') { 1225 channelId = payload.channel_id.trim(); 1226 } 1227 1228 var persistedDecision = payload.persisted_decision === true; 1229 var resolvedCategories = payload.categories && typeof payload.categories === 'object' 1230 ? payload.categories 1231 : currentState.categories; 1232 1233 return applyState(resolvedCategories, persistedDecision, 'runtime:sync_state', { 1234 markVerified: true 1235 }); 1236 }); 1237 } 1238 1239 function activateBlockedScriptsForGrantedCategories() { 1240 if (!global.document || typeof global.document.querySelectorAll !== 'function') { 1241 return; 1242 } 1243 1244 var blockedScripts = global.document.querySelectorAll('script[type="text/plain"][data-icm-category]'); 1245 for (var i = 0; i < blockedScripts.length; i++) { 1246 var blockedScript = blockedScripts[i]; 1247 var category = String(blockedScript.getAttribute('data-icm-category') || '').trim(); 1248 if (!category || !currentState.categories[category]) { 1249 continue; 1250 } 1251 1252 if (blockedScript.getAttribute('data-icm-activated') === '1') { 1253 continue; 1254 } 1255 1256 var activated = global.document.createElement('script'); 1257 for (var attrIndex = 0; attrIndex < blockedScript.attributes.length; attrIndex++) { 1258 var attribute = blockedScript.attributes[attrIndex]; 1259 if ( 1260 attribute.name === 'type' 1261 || attribute.name === 'data-icm-category' 1262 || attribute.name === 'data-icm-activated' 1263 || attribute.name === 'data-icm-original-type' 1264 || attribute.name === 'data-icm-src' 1265 ) { 1266 continue; 1267 } 1268 activated.setAttribute(attribute.name, attribute.value); 1269 } 1270 1271 var originalType = String(blockedScript.getAttribute('data-icm-original-type') || '').trim(); 1272 if (originalType !== '') { 1273 activated.setAttribute('type', originalType); 1274 } 1275 1276 var originalSrc = String(blockedScript.getAttribute('data-icm-src') || '').trim(); 1277 if (originalSrc !== '' && !activated.hasAttribute('src')) { 1278 activated.setAttribute('src', originalSrc); 1279 } 1280 1281 activated.text = blockedScript.text || blockedScript.textContent || ''; 1282 blockedScript.setAttribute('data-icm-activated', '1'); 1283 blockedScript.parentNode.insertBefore(activated, blockedScript.nextSibling); 1284 } 1285 1286 var blockedEmbeds = global.document.querySelectorAll('iframe[data-icm-category][data-icm-src],embed[data-icm-category][data-icm-src],object[data-icm-category][data-icm-src]'); 1287 for (var j = 0; j < blockedEmbeds.length; j++) { 1288 var blockedEmbed = blockedEmbeds[j]; 1289 var embedCategory = String(blockedEmbed.getAttribute('data-icm-category') || '').trim(); 1290 if (!embedCategory || !currentState.categories[embedCategory]) { 1291 continue; 1292 } 1293 1294 if (blockedEmbed.getAttribute('data-icm-activated') === '1') { 1295 continue; 1296 } 1297 1298 var sourceUrl = String(blockedEmbed.getAttribute('data-icm-src') || '').trim(); 1299 if (sourceUrl === '') { 1300 continue; 1301 } 1302 1303 var sourceAttr = String(blockedEmbed.getAttribute('data-icm-source-attr') || '').trim().toLowerCase(); 1304 if (sourceAttr === '') { 1305 sourceAttr = blockedEmbed.tagName && blockedEmbed.tagName.toLowerCase() === 'object' ? 'data' : 'src'; 1306 } 1307 if (sourceAttr !== 'src' && sourceAttr !== 'data') { 1308 continue; 1309 } 1310 1311 blockedEmbed.setAttribute(sourceAttr, sourceUrl); 1312 blockedEmbed.setAttribute('data-icm-activated', '1'); 1313 } 1314 } 1315 1316 function deactivateRevokedCategories(previousCategories, nextCategories) { 1317 if (!global.document) { 1318 return; 1319 } 1320 1321 if (typeof global.CustomEvent === 'function') { 1322 global.dispatchEvent(new CustomEvent('IntegratedConsentCategoriesRevoked', { 1323 detail: { 1324 previousCategories: previousCategories || {}, 1325 categories: nextCategories || {} 1326 } 1327 })); 1328 } 1329 } 1330 1331 function escapeHtml(value) { 1332 return String(value || '') 1333 .replace(/&/g, '&') 1334 .replace(/</g, '<') 1335 .replace(/>/g, '>') 1336 .replace(/"/g, '"') 1337 .replace(/'/g, '''); 1338 } 1339 1340 function applyPrivacyLinkAttributes(link) { 1341 if (!link) { 1342 return; 1343 } 1344 1345 link.setAttribute('href', privacyPolicyUrl); 1346 if (designSettings.privacyPolicyLinkTarget === 'new_window') { 1347 link.setAttribute('target', '_blank'); 1348 link.setAttribute('rel', 'noopener noreferrer'); 1349 return; 1350 } 1351 1352 link.removeAttribute('target'); 1353 link.removeAttribute('rel'); 1354 } 1355 1356 function buildAboutTextHtml() { 1357 var aboutText = String(translations.aboutText || '').trim(); 1358 var privacyLabel = String(translations.privacyPolicy || '').trim(); 1359 1360 if (!designSettings.showPrivacyPolicyLink || privacyLabel === '') { 1361 return '<p>' + escapeHtml(aboutText) + '</p>'; 1362 } 1363 1364 var labelIndex = aboutText.lastIndexOf(privacyLabel); 1365 if (labelIndex === -1) { 1366 return '' 1367 + '<p>' + escapeHtml(aboutText) + '</p>' 1368 + '<p><a data-icm-about-privacy-link>' + escapeHtml(privacyLabel) + '</a></p>'; 1369 } 1370 1371 var before = aboutText.slice(0, labelIndex); 1372 var after = aboutText.slice(labelIndex + privacyLabel.length); 1373 1374 return '' 1375 + '<p>' 1376 + escapeHtml(before) 1377 + '<a data-icm-about-privacy-link>' + escapeHtml(privacyLabel) + '</a>' 1378 + escapeHtml(after) 1379 + '</p>'; 1380 } 1381 1382 function applyBannerVisualSettings(banner) { 1383 if (!banner) { 1384 return; 1385 } 1386 1387 var effectiveBannerVerticalPosition = designSettings.bannerStyle === 'centered' 1388 ? 'center' 1389 : designSettings.bannerVerticalPosition; 1390 banner.className = [ 1391 'icm-consent-banner', 1392 'icm-consent-theme--' + designSettings.themeVariant, 1393 'icm-consent-banner--style-' + designSettings.bannerStyle, 1394 'icm-consent-banner--vertical-' + effectiveBannerVerticalPosition, 1395 'icm-consent-banner--horizontal-' + designSettings.bannerHorizontalPosition, 1396 'icm-consent-banner--corner-' + designSettings.bannerCornerStyle 1397 ].join(' '); 1398 banner.style.setProperty('--icm-brand-color', brandColor); 1399 banner.style.setProperty('--icm-brand-strong', brandStrongColor); 1400 1401 var logo = banner.querySelector('[data-icm-banner-logo]'); 1402 if (logo) { 1403 if (designSettings.showCookieLogo) { 1404 logo.removeAttribute('hidden'); 1405 } else { 1406 logo.setAttribute('hidden', 'hidden'); 1407 } 1408 } 1409 1410 var closeButton = banner.querySelector('[data-icm-action="dismiss-banner"]'); 1411 if (closeButton) { 1412 if (designSettings.showCloseIcon) { 1413 closeButton.removeAttribute('hidden'); 1414 } else { 1415 closeButton.setAttribute('hidden', 'hidden'); 1416 } 1417 } 1418 1419 var privacyLink = banner.querySelector('[data-icm-banner-privacy-link]'); 1420 if (privacyLink) { 1421 if (designSettings.showPrivacyPolicyLink) { 1422 privacyLink.removeAttribute('hidden'); 1423 applyPrivacyLinkAttributes(privacyLink); 1424 } else { 1425 privacyLink.setAttribute('hidden', 'hidden'); 1426 } 1427 } 1428 } 1429 1430 function applyManageButtonVisualSettings(button) { 1431 if (!button) { 1432 return; 1433 } 1434 1435 button.className = [ 1436 'icm-consent-manage-button', 1437 'icm-consent-theme--' + designSettings.themeVariant, 1438 'icm-consent-manage-button--vertical-' + designSettings.bannerVerticalPosition, 1439 'icm-consent-manage-button--horizontal-' + designSettings.bannerHorizontalPosition, 1440 'icm-consent-manage-button--corner-' + designSettings.bannerCornerStyle 1441 ].join(' '); 1442 button.style.setProperty('--icm-brand-color', brandColor); 1443 button.style.setProperty('--icm-brand-strong', brandStrongColor); 1444 } 1445 1446 function applyModalVisualSettings(modal) { 1447 if (!modal) { 1448 return; 1449 } 1450 1451 var dialog = modal.querySelector('.icm-consent-modal__dialog'); 1452 if (dialog) { 1453 dialog.classList.remove('icm-consent-theme--dark', 'icm-consent-theme--light'); 1454 dialog.classList.add('icm-consent-theme--' + designSettings.themeVariant); 1455 dialog.style.setProperty('--icm-brand-color', brandColor); 1456 dialog.style.setProperty('--icm-brand-strong', brandStrongColor); 1457 } 1458 1459 var modalCloseButton = modal.querySelector('.icm-consent-modal__close'); 1460 if (modalCloseButton) { 1461 if (designSettings.showCloseIcon) { 1462 modalCloseButton.removeAttribute('hidden'); 1463 } else { 1464 modalCloseButton.setAttribute('hidden', 'hidden'); 1465 } 1466 } 1467 1468 var modalPrivacyLink = modal.querySelector('[data-icm-modal-privacy-link]'); 1469 if (modalPrivacyLink) { 1470 if (designSettings.showPrivacyPolicyLink) { 1471 modalPrivacyLink.removeAttribute('hidden'); 1472 applyPrivacyLinkAttributes(modalPrivacyLink); 1473 } else { 1474 modalPrivacyLink.setAttribute('hidden', 'hidden'); 1475 } 1476 } 1477 1478 var aboutPrivacyLink = modal.querySelector('[data-icm-about-privacy-link]'); 1479 if (aboutPrivacyLink) { 1480 if (designSettings.showPrivacyPolicyLink) { 1481 aboutPrivacyLink.removeAttribute('hidden'); 1482 applyPrivacyLinkAttributes(aboutPrivacyLink); 1483 } else { 1484 aboutPrivacyLink.setAttribute('hidden', 'hidden'); 1485 } 1486 } 1487 } 1488 1489 function applyQuickPanelVisualSettings(panel) { 1490 if (!panel) { 1491 return; 1492 } 1493 1494 var effectiveQuickPanelVerticalPosition = designSettings.bannerStyle === 'centered' 1495 ? 'center' 1496 : designSettings.bannerVerticalPosition; 1497 panel.className = [ 1498 'icm-consent-quick-panel', 1499 'icm-consent-theme--' + designSettings.themeVariant, 1500 'icm-consent-quick-panel--style-' + designSettings.bannerStyle,
1501 'icm-consent-quick-panel--vertical-' + effectiveQuickPanelVerticalPosition, 1502 'icm-consent-quick-panel--horizontal-' + designSettings.bannerHorizontalPosition, 1503 'icm-consent-quick-panel--corner-' + designSettings.bannerCornerStyle 1504 ].join(' '); 1505 panel.style.setProperty('--icm-brand-color', brandColor); 1506 panel.style.setProperty('--icm-brand-strong', brandStrongColor); 1507 1508 var logo = panel.querySelector('[data-icm-quick-logo]'); 1509 if (logo) { 1510 if (designSettings.showCookieLogo) { 1511 logo.removeAttribute('hidden'); 1512 } else { 1513 logo.setAttribute('hidden', 'hidden'); 1514 } 1515 } 1516 1517 var closeButton = panel.querySelector('[data-icm-action="close-quick-panel"]'); 1518 if (closeButton) { 1519 if (designSettings.showCloseIcon) { 1520 closeButton.removeAttribute('hidden'); 1521 } else { 1522 closeButton.setAttribute('hidden', 'hidden'); 1523 } 1524 } 1525 1526 var privacyLink = panel.querySelector('[data-icm-quick-privacy-link]'); 1527 if (privacyLink) { 1528 if (designSettings.showPrivacyPolicyLink) { 1529 privacyLink.removeAttribute('hidden'); 1530 applyPrivacyLinkAttributes(privacyLink); 1531 } else { 1532 privacyLink.setAttribute('hidden', 'hidden'); 1533 } 1534 } 1535 } 1536 1537 function renderQuickPanelOptions(panel) { 1538 if (!panel) { 1539 return; 1540 } 1541 1542 var optionsContainer = panel.querySelector('[data-icm-quick-options]'); 1543 if (!optionsContainer) { 1544 return; 1545 } 1546 1547 var categoryOrder = getCategoryDisplayOrder(); 1548 var html = ''; 1549 for (var i = 0; i < categoryOrder.length; i++) { 1550 var category = categoryOrder[i]; 1551 var metadata = categoryMetadata[category] || {label: category, description: ''}; 1552 var label = metadata.label || category; 1553 var checked = currentState.categories[category] === true ? ' checked' : ''; 1554 var disabled = isAlwaysOnCategory(category) ? ' disabled' : ''; 1555 1556 html += '' 1557 + '<label class="icm-consent-quick-panel__option">' 1558 + ' <input type="checkbox" data-icm-quick-toggle="' + escapeHtml(category) + '"' + checked + disabled + '>' 1559 + ' <span>' + escapeHtml(label) + '</span>' 1560 + '</label>'; 1561 } 1562 1563 optionsContainer.innerHTML = html; 1564 } 1565 1566 function buildCategoriesFromQuickPanel(panel) { 1567 var next = normalizeCategories(currentState.categories); 1568 var optionalCategories = getOptionalCategories(); 1569 1570 for (var i = 0; i < optionalCategories.length; i++) { 1571 var category = optionalCategories[i]; 1572 var selector = '[data-icm-quick-toggle="' + category + '"]'; 1573 var checkbox = panel.querySelector(selector); 1574 next[category] = !!(checkbox && checkbox.checked); 1575 } 1576 1577 return enforceAlwaysOnCategories(next); 1578 } 1579 1580 function areCategoryStatesEqual(left, right) { 1581 var normalizedLeft = normalizeCategories(left || {}); 1582 var normalizedRight = normalizeCategories(right || {}); 1583 for (var i = 0; i < categories.length; i++) { 1584 var key = categories[i]; 1585 if (normalizedLeft[key] !== normalizedRight[key]) { 1586 return false; 1587 } 1588 } 1589 1590 return true; 1591 } 1592 1593 function syncQuickPanelPrimaryAction(panel) { 1594 if (!panel) { 1595 return; 1596 } 1597 1598 var primaryButton = panel.querySelector('[data-icm-action="accept-all"]'); 1599 if (!primaryButton) { 1600 return; 1601 } 1602 1603 var baseline = panel.__icmInitialCategories && typeof panel.__icmInitialCategories === 'object' 1604 ? panel.__icmInitialCategories 1605 : normalizeCategories(currentState.categories); 1606 var categoriesFromPanel = buildCategoriesFromQuickPanel(panel); 1607 var hasManualChanges = !areCategoryStatesEqual(categoriesFromPanel, baseline); 1608 1609 if (hasManualChanges) { 1610 primaryButton.textContent = translations.savePreferences; 1611 primaryButton.setAttribute('data-icm-primary-mode', 'save_preferences'); 1612 return; 1613 } 1614 1615 primaryButton.textContent = translations.bannerAcceptAll; 1616 primaryButton.setAttribute('data-icm-primary-mode', 'accept_all'); 1617 } 1618 1619 function clearQuickPanelPersistDebounce() { 1620 if (quickPanelPersistDebounceTimer !== null && typeof global.clearTimeout === 'function') { 1621 global.clearTimeout(quickPanelPersistDebounceTimer); 1622 quickPanelPersistDebounceTimer = null; 1623 } 1624 } 1625 1626 function ensureQuickPanel() { 1627 if (!global.document || !global.document.body) { 1628 return null; 1629 } 1630 1631 if (ui.quickPanel !== null) { 1632 applyQuickPanelVisualSettings(ui.quickPanel); 1633 return ui.quickPanel; 1634 } 1635 1636 var panel = global.document.createElement('section'); 1637 panel.className = 'icm-consent-quick-panel'; 1638 panel.setAttribute('role', 'dialog'); 1639 panel.setAttribute('aria-modal', 'false'); 1640 panel.setAttribute('aria-label', 'Cookie instellingen'); 1641 panel.setAttribute('hidden', 'hidden'); 1642 panel.innerHTML = '' 1643 + '<div class="icm-consent-quick-panel__meta">' 1644 + ' <span class="icm-consent-quick-panel__logo" data-icm-quick-logo><span class="icm-consent-quick-panel__logo-dot">â</span> Cookie</span>' 1645 + ' <button type="button" class="icm-consent-quick-panel__close" data-icm-action="close-quick-panel" aria-label="Close panel">Ã</button>' 1646 + '</div>' 1647 + '<h3>' + escapeHtml(translations.bannerTitle) + '</h3>' 1648 + '<p>' + escapeHtml(translations.bannerDescription) + '</p>' 1649 + '<a class="icm-consent-quick-panel__privacy-link" data-icm-quick-privacy-link>' + escapeHtml(translations.privacyPolicy) + '</a>' 1650 + '<div class="icm-consent-quick-panel__options" data-icm-quick-options></div>' 1651 + '<p class="icm-consent-feedback" data-icm-feedback hidden></p>' 1652 + '<div class="icm-consent-quick-panel__actions">' 1653 + ' <button type="button" class="icm-btn icm-btn--primary" data-icm-action="accept-all" data-icm-primary-mode="accept_all" data-icm-request-button>' + escapeHtml(translations.bannerAcceptAll) + '</button>' 1654 + ' <button type="button" class="icm-btn" data-icm-action="reject-optional" data-icm-request-button>' + escapeHtml(translations.bannerRejectOptional) + '</button>' 1655 + ' <button type="button" class="icm-consent-inline-action" data-icm-action="open-preferences">' + escapeHtml(translations.bannerOpenPreferences) + '</button>' 1656 + '</div>'; 1657 1658 global.document.body.appendChild(panel); 1659 1660 var closePanel = panel.querySelector('[data-icm-action="close-quick-panel"]'); 1661 if (closePanel) { 1662 closePanel.addEventListener('click', function () { 1663 closeQuickPanel(); 1664 }); 1665 } 1666 1667 var acceptAll = panel.querySelector('[data-icm-action="accept-all"]'); 1668 if (acceptAll) { 1669 acceptAll.addEventListener('click', function () { 1670 clearQuickPanelPersistDebounce(); 1671 var mode = String(acceptAll.getAttribute('data-icm-primary-mode') || 'accept_all').trim().toLowerCase(); 1672 1673 if (mode === 'save_preferences') { 1674 var categoriesFromPanel = buildCategoriesFromQuickPanel(panel); 1675 global.IntegratedConsentManager.persistDecision(categoriesFromPanel, { 1676 source: 'quick_panel:save_preferences' 1677 }).then(function () { 1678 closeQuickPanel(); 1679 }).catch(function () { 1680 return null; 1681 }); 1682 return; 1683 } 1684 1685 global.IntegratedConsentManager.persistDecision(buildAcceptAllState(), { 1686 source: 'quick_panel:accept_all_primary' 1687 }).then(function () { 1688 closeQuickPanel(); 1689 }).catch(function () { 1690 return null; 1691 }); 1692 }); 1693 } 1694 1695 var rejectOptional = panel.querySelector('[data-icm-action="reject-optional"]'); 1696 if (rejectOptional) { 1697 rejectOptional.addEventListener('click', function () { 1698 clearQuickPanelPersistDebounce(); 1699 global.IntegratedConsentManager.revoke({ 1700 source: 'quick_panel:reject_optional' 1701 }).then(function () { 1702 closeQuickPanel(); 1703 }).catch(function () { 1704 return null; 1705 }); 1706 }); 1707 } 1708 1709 var openPreferences = panel.querySelector('[data-icm-action="open-preferences"]'); 1710 if (openPreferences) { 1711 openPreferences.addEventListener('click', function () { 1712 clearQuickPanelPersistDebounce(); 1713 var categoriesFromPanel = buildCategoriesFromQuickPanel(panel); 1714 closeQuickPanel(true); 1715 openPreferencesModal(categoriesFromPanel, {returnSurface: 'quick_panel'}); 1716 }); 1717 } 1718 1719 panel.addEventListener('change', function (event) { 1720 var target = event && event.target ? event.target : null; 1721 if (!target || !target.matches || !target.matches('[data-icm-quick-toggle]')) { 1722 return; 1723 } 1724 syncQuickPanelPrimaryAction(panel); 1725 }); 1726 1727 applyQuickPanelVisualSettings(panel); 1728 ui.quickPanel = panel; 1729 1730 return panel; 1731 } 1732 1733 function ensureConsentBanner() { 1734 if (!global.document || !global.document.body) { 1735 return null; 1736 } 1737 1738 if (ui.bannerOverlay === null) {
1739 var overlay = global.document.createElement('div'); 1740 overlay.className = 'icm-consent-banner-overlay'; 1741 overlay.setAttribute('hidden', 'hidden'); 1742 global.document.body.appendChild(overlay); 1743 ui.bannerOverlay = overlay; 1744 } 1745 1746 if (ui.banner !== null) { 1747 applyBannerVisualSettings(ui.banner); 1748 return ui.banner; 1749 } 1750 1751 var banner = global.document.createElement('section'); 1752 banner.className = 'icm-consent-banner'; 1753 banner.setAttribute('role', 'region'); 1754 banner.setAttribute('aria-label', 'Consent manager'); 1755 banner.innerHTML = '' 1756 + '<div class="icm-consent-banner__content">' 1757 + ' <div class="icm-consent-banner__meta">' 1758 + ' <span class="icm-consent-banner__logo" data-icm-banner-logo><span class="icm-consent-banner__logo-dot">â</span> Cookie</span>' 1759 + ' <button type="button" class="icm-consent-banner__close" data-icm-action="dismiss-banner" aria-label="Close banner">Ã</button>' 1760 + ' </div>' 1761 + ' <h2>' + escapeHtml(translations.bannerTitle) + '</h2>' 1762 + ' <p>' + escapeHtml(translations.bannerDescription) + '</p>' 1763 + ' <a class="icm-consent-banner__privacy-link" data-icm-banner-privacy-link>' + escapeHtml(translations.privacyPolicy) + '</a>' 1764 + ' <p class="icm-consent-banner__scope" data-icm-scope-text></p>' 1765 + ' <p class="icm-consent-feedback" data-icm-feedback hidden></p>' 1766 + '</div>' 1767 + '<div class="icm-consent-banner__actions">' 1768 + ' <button type="button" class="icm-btn icm-btn--primary" data-icm-action="accept-all" data-icm-request-button>' + escapeHtml(translations.bannerAcceptAll) + '</button>' 1769 + ' <button type="button" class="icm-btn" data-icm-action="reject-optional" data-icm-request-button>' + escapeHtml(translations.bannerRejectOptional) + '</button>' 1770 + ' <button type="button" class="icm-consent-inline-action" data-icm-action="open-preferences">' + escapeHtml(translations.bannerOpenPreferences) + '</button>' 1771 + '</div>'; 1772 1773 global.document.body.appendChild(banner); 1774 1775 var acceptAll = banner.querySelector('[data-icm-action="accept-all"]'); 1776 if (acceptAll) { 1777 acceptAll.addEventListener('click', function () { 1778 global.IntegratedConsentManager.persistDecision(buildAcceptAllState(), { 1779 source: 'banner:accept_all' 1780 }).catch(function () { 1781 return null; 1782 }); 1783 }); 1784 } 1785 1786 var rejectOptional = banner.querySelector('[data-icm-action="reject-optional"]'); 1787 if (rejectOptional) { 1788 rejectOptional.addEventListener('click', function () { 1789 global.IntegratedConsentManager.revoke({ 1790 source: 'banner:reject_optional' 1791 }).catch(function () { 1792 return null; 1793 }); 1794 }); 1795 } 1796 1797 var openPreferences = banner.querySelector('[data-icm-action="open-preferences"]'); 1798 if (openPreferences) { 1799 openPreferences.addEventListener('click', function () { 1800 openPreferencesModal(); 1801 }); 1802 } 1803 1804 var dismissBanner = banner.querySelector('[data-icm-action="dismiss-banner"]'); 1805 if (dismissBanner) { 1806 dismissBanner.addEventListener('click', function () { 1807 bannerDismissed = true; 1808 renderConsentUi(); 1809 }); 1810 } 1811 1812 applyBannerVisualSettings(banner); 1813 1814 ui.banner = banner; 1815 1816 return banner; 1817 } 1818 1819 function ensurePreferencesModal() { 1820 if (!global.document || !global.document.body) { 1821 return null; 1822 } 1823 1824 if (ui.modal !== null) { 1825 return ui.modal; 1826 } 1827 1828 var modal = global.document.createElement('div'); 1829 modal.className = 'icm-consent-modal'; 1830 modal.setAttribute('hidden', 'hidden'); 1831 modal.innerHTML = '' 1832 + '<div class="icm-consent-modal__overlay" data-icm-modal-close></div>' 1833 + '<div class="icm-consent-modal__dialog" role="dialog" aria-modal="true" aria-label="Cookie preferences">' 1834 + ' <button type="button" class="icm-consent-modal__close" data-icm-modal-close aria-label="Close">Ã</button>' 1835 + ' <h3>' + escapeHtml(translations.modalTitle) + '</h3>' 1836 + ' <p>'
1837 + escapeHtml(translations.modalDescription) 1838 + ' <a data-icm-modal-privacy-link>' 1839 + escapeHtml(translations.privacyPolicy) 1840 + '</a></p>' 1841 + ' <p class="icm-consent-modal__scope" data-icm-scope-text></p>' 1842 + ' <div class="icm-consent-modal__tabs">' 1843 + ' <button type="button" class="icm-consent-modal__tab is-active" data-icm-tab="declaration">' + escapeHtml(translations.declarationTab) + '</button>' 1844 + ' <button type="button" class="icm-consent-modal__tab" data-icm-tab="about">' + escapeHtml(translations.aboutTab) + '</button>' 1845 + ' </div>' 1846 + ' <div class="icm-consent-modal__tab-panel" data-icm-tab-content="declaration">' 1847 + ' <div class="icm-consent-modal__options" data-icm-modal-options></div>' 1848 + ' </div>' 1849 + ' <div class="icm-consent-modal__tab-panel" data-icm-tab-content="about" hidden>' 1850 + buildAboutTextHtml() 1851 + ' </div>' 1852 + ' <p class="icm-consent-feedback" data-icm-feedback hidden></p>' 1853 + ' <div class="icm-consent-modal__actions">' 1854 + ' <button type="button" class="icm-btn icm-btn--primary" data-icm-action="accept-all" data-icm-request-button>' + escapeHtml(translations.bannerAcceptAll) + '</button>' 1855 + ' <button type="button" class="icm-btn" data-icm-action="reject-optional" data-icm-request-button>' + escapeHtml(translations.bannerRejectOptional) + '</button>' 1856 + ' <button type="button" class="icm-btn" data-icm-action="save-preferences" data-icm-request-button>' + escapeHtml(translations.savePreferences) + '</button>' 1857 + ' </div>' 1858 + '</div>'; 1859 1860 global.document.body.appendChild(modal); 1861 1862 var modalCloseTargets = modal.querySelectorAll('[data-icm-modal-close]'); 1863 for (var i = 0; i < modalCloseTargets.length; i++) { 1864 modalCloseTargets[i].addEventListener('click', function () { 1865 closePreferencesModal(); 1866 }); 1867 } 1868 1869 var savePreferences = modal.querySelector('[data-icm-action="save-preferences"]'); 1870 if (savePreferences) { 1871 savePreferences.addEventListener('click', function () { 1872 var categoriesFromModal = buildCategoriesFromModal(modal); 1873 global.IntegratedConsentManager.persistDecision(categoriesFromModal, { 1874 source: 'modal:save_preferences' 1875 }).catch(function () { 1876 return null; 1877 }); 1878 }); 1879 } 1880 1881 var modalAcceptAll = modal.querySelector('[data-icm-action="accept-all"]'); 1882 if (modalAcceptAll) { 1883 modalAcceptAll.addEventListener('click', function () { 1884 global.IntegratedConsentManager.persistDecision(buildAcceptAllState(), { 1885 source: 'modal:accept_all' 1886 }).catch(function () { 1887 return null; 1888 }); 1889 }); 1890 } 1891 1892 var modalRejectOptional = modal.querySelector('[data-icm-action="reject-optional"]'); 1893 if (modalRejectOptional) { 1894 modalRejectOptional.addEventListener('click', function () { 1895 global.IntegratedConsentManager.revoke({ 1896 source: 'modal:reject_optional' 1897 }).catch(function () { 1898 return null; 1899 }); 1900 }); 1901 } 1902 1903 wireModalTabs(modal); 1904 applyModalVisualSettings(modal); 1905 1906 if (global.document.addEventListener) { 1907 global.document.addEventListener('keydown', function (event) { 1908 if (event && event.key === 'Escape') { 1909 closePreferencesModal(); 1910 return; 1911 } 1912 if (!behaviorSettings.focusTrapEnabled || !event || event.key !== 'Tab') { 1913 return; 1914 } 1915 1916 if (ui.modal && !ui.modal.hasAttribute('hidden')) { 1917 trapFocusInContainer(event, ui.modal.querySelector('.icm-consent-modal__dialog')); 1918 return; 1919 } 1920 1921 if (ui.quickPanel && !ui.quickPanel.hasAttribute('hidden')) { 1922 trapFocusInContainer(event, ui.quickPanel); 1923 return; 1924 } 1925 1926 if (ui.banner && !ui.banner.hasAttribute('hidden')) { 1927 trapFocusInContainer(event, ui.banner); 1928 } 1929 }); 1930 } 1931 1932 ui.modal = modal; 1933 1934 return modal; 1935 } 1936 1937 function ensureManageButton() { 1938 if (!global.document || !global.document.body) { 1939 return null; 1940 } 1941 1942 if (ui.manageButton !== null) { 1943 applyManageButtonVisualSettings(ui.manageButton); 1944 return ui.manageButton; 1945 } 1946 1947 var button = global.document.createElement('button'); 1948 button.type = 'button'; 1949 button.className = 'icm-consent-manage-button'; 1950 button.setAttribute('data-icm-action', 'open-preferences-persistent'); 1951 button.textContent = translations.manageButton; 1952 button.addEventListener('click', function () { 1953 openQuickPanel(); 1954 }); 1955 1956 global.document.body.appendChild(button); 1957 applyManageButtonVisualSettings(button); 1958 ui.manageButton = button; 1959 1960 return button; 1961 } 1962 1963 function openQuickPanel() { 1964 var panel = ensureQuickPanel(); 1965 if (!panel) { 1966 return; 1967 } 1968 1969 closePreferencesModal(true); 1970 renderQuickPanelOptions(panel); 1971 panel.__icmInitialCategories = normalizeCategories(currentState.categories); 1972 syncQuickPanelPrimaryAction(panel); 1973 quickPanelVisible = true; 1974 panel.removeAttribute('hidden'); 1975 if (ui.manageButton) { 1976 ui.manageButton.setAttribute('hidden', 'hidden'); 1977 } 1978 renderConsentUi(); 1979 } 1980 1981 function closeQuickPanel(skipRender) { 1982 clearQuickPanelPersistDebounce(); 1983 1984 quickPanelVisible = false;
1985 if (!currentState.persistedDecision && shouldUseQuickPanelAsPrimary()) { 1986 bannerDismissed = true; 1987 } 1988 if (!ui.quickPanel) { 1989 if (!skipRender) { 1990 renderConsentUi(); 1991 } 1992 return; 1993 } 1994 1995 ui.quickPanel.setAttribute('hidden', 'hidden'); 1996 if (skipRender && ui.bannerOverlay) { 1997 ui.bannerOverlay.setAttribute('hidden', 'hidden'); 1998 } 1999 if (!skipRender) { 2000 renderConsentUi(); 2001 } 2002 } 2003 2004 function shouldUseQuickPanelAsPrimary() { 2005 // Show the checkbox-based consent panel as first-visit prompt. 2006 return true; 2007 } 2008 2009 function wireModalTabs(modal) { 2010 var tabButtons = modal.querySelectorAll('[data-icm-tab]'); 2011 for (var i = 0; i < tabButtons.length; i++) { 2012 tabButtons[i].addEventListener('click', function () { 2013 var tabKey = String(this.getAttribute('data-icm-tab') || '').trim(); 2014 if (tabKey === '') { 2015 return; 2016 } 2017 2018 openModalTab(modal, tabKey); 2019 }); 2020 } 2021 } 2022 2023 function openModalTab(modal, tabKey) { 2024 var tabButtons = modal.querySelectorAll('[data-icm-tab]'); 2025 for (var i = 0; i < tabButtons.length; i++) { 2026 var button = tabButtons[i]; 2027 if (String(button.getAttribute('data-icm-tab') || '') === tabKey) { 2028 button.classList.add('is-active'); 2029 } else { 2030 button.classList.remove('is-active'); 2031 } 2032 } 2033 2034 var tabPanels = modal.querySelectorAll('[data-icm-tab-content]'); 2035 for (var panelIndex = 0; panelIndex < tabPanels.length; panelIndex++) { 2036 var panel = tabPanels[panelIndex]; 2037 if (String(panel.getAttribute('data-icm-tab-content') || '') === tabKey) { 2038 panel.removeAttribute('hidden'); 2039 } else { 2040 panel.setAttribute('hidden', 'hidden'); 2041 } 2042 } 2043 } 2044 2045 function renderPreferencesOptions(modal, categoriesSnapshot) { 2046 if (!modal) { 2047 return; 2048 } 2049 2050 var optionsContainer = modal.querySelector('[data-icm-modal-options]'); 2051 if (!optionsContainer) { 2052 return; 2053 } 2054 2055 var resolvedCategories = normalizeCategories(categoriesSnapshot || currentState.categories); 2056 var categoryOrder = getCategoryDisplayOrder(); 2057 var html = ''; 2058 2059 for (var i = 0; i < categoryOrder.length; i++) { 2060 var category = categoryOrder[i]; 2061 var metadata = categoryMetadata[category] || {label: category, description: ''}; 2062 var label = metadata.label || category; 2063 var description = metadata.description || ''; 2064 var checked = resolvedCategories[category] === true ? ' checked' : ''; 2065 var disabled = isAlwaysOnCategory(category) ? ' disabled' : ''; 2066 var cookieRows = Array.isArray(cookieDeclaration[category]) ? cookieDeclaration[category] : []; 2067 var declarationHtml = ''; 2068 2069 if (cookieRows.length === 0) { 2070 declarationHtml = '<p class="icm-consent-modal__empty">' + escapeHtml(translations.noCookiesDetected) + '</p>'; 2071 } else { 2072 declarationHtml += '<table class="icm-consent-modal__cookie-table">'; 2073 declarationHtml += '<thead><tr>' 2074 + '<th>' + escapeHtml(translations.cookieName) + '</th>' 2075 + '<th>' + escapeHtml(translations.cookieProviderDomain) + '</th>' 2076 + '<th>' + escapeHtml(translations.cookieExpiry) + '</th>' 2077 + '<th>' + escapeHtml(translations.cookieDescription) + '</th>' 2078 + '</tr></thead><tbody>'; 2079 2080 for (var rowIndex = 0; rowIndex < cookieRows.length; rowIndex++) { 2081 var row = cookieRows[rowIndex] || {}; 2082 var providerDomain = String(row.provider || '').trim(); 2083 var domain = String(row.domain || '').trim(); 2084 if (providerDomain !== '' && domain !== '') { 2085 providerDomain = providerDomain + ' / ' + domain; 2086 } else if (domain !== '') { 2087 providerDomain = domain; 2088 } 2089 2090 declarationHtml += '<tr>' 2091 + '<td>' + escapeHtml(row.name || '') + '</td>' 2092 + '<td>' + escapeHtml(providerDomain) + '</td>' 2093 + '<td>' + escapeHtml(row.expiry || '') + '</td>' 2094 + '<td>' 2095 + escapeHtml(row.description || '') 2096 + (row.evidence ? '<div class="icm-consent-modal__cookie-evidence">' + escapeHtml(row.evidence) + '</div>' : '') 2097 + '</td>' 2098 + '</tr>'; 2099 } 2100 2101 declarationHtml += '</tbody></table>'; 2102 } 2103 2104 html += '' 2105 + '<section class="icm-consent-modal__category">' 2106 + ' <div class="icm-consent-modal__category-header">' 2107 + ' <div>' 2108 + ' <h4>' + escapeHtml(label) + '</h4>' 2109 + ' <p>' + escapeHtml(description) + '</p>'
2110 + ' </div>' 2111 + ' <label class="icm-consent-modal__switch">' 2112 + ' <input type="checkbox" data-icm-category-toggle="' + escapeHtml(category) + '"' + checked + disabled + '>' 2113 + ' <span class="icm-consent-modal__switch-slider" aria-hidden="true"></span>' 2114 + ' </label>' 2115 + ' </div>' 2116 + ' <details class="icm-consent-modal__cookie-details">' 2117 + ' <summary>' + escapeHtml(translations.showCookies) + '</summary>' 2118 + declarationHtml 2119 + ' </details>' 2120 + '</section>'; 2121 } 2122 2123 optionsContainer.innerHTML = html; 2124 } 2125 2126 function buildCategoriesFromModal(modal) { 2127 var next = normalizeCategories(currentState.categories); 2128 var optionalCategories = getOptionalCategories(); 2129 2130 for (var i = 0; i < optionalCategories.length; i++) { 2131 var category = optionalCategories[i]; 2132 var selector = '[data-icm-category-toggle="' + category + '"]'; 2133 var checkbox = modal.querySelector(selector); 2134 next[category] = !!(checkbox && checkbox.checked); 2135 } 2136 2137 return enforceAlwaysOnCategories(next); 2138 } 2139 2140 function openPreferencesModal(categoriesSnapshot, options) { 2141 var modal = ensurePreferencesModal(); 2142 if (!modal) { 2143 return; 2144 } 2145 2146 var opts = options && typeof options === 'object' ? options : {}; 2147 modalReturnSurface = opts.returnSurface === 'quick_panel' ? 'quick_panel' : 'banner'; 2148 closeQuickPanel(true); 2149 renderPreferencesOptions(modal, categoriesSnapshot || null); 2150 openModalTab(modal, 'declaration'); 2151 modal.removeAttribute('hidden'); 2152 } 2153 2154 function closePreferencesModal(skipRender) { 2155 if (!ui.modal) { 2156 if (!skipRender) { 2157 renderConsentUi(); 2158 } 2159 return; 2160 } 2161 2162 ui.modal.setAttribute('hidden', 'hidden'); 2163 if (skipRender) { 2164 modalReturnSurface = 'banner'; 2165 return; 2166 } 2167 2168 var shouldReturnToQuickPanel = modalReturnSurface === 'quick_panel' && !currentState.persistedDecision; 2169 modalReturnSurface = 'banner'; 2170 if (shouldReturnToQuickPanel) { 2171 openQuickPanel(); 2172 return; 2173 } 2174 renderConsentUi(); 2175 } 2176 2177 function resolveScopeLabel() { 2178 if (scopeLabel !== '') { 2179 return scopeLabel; 2180 } 2181 2182 var label = channelLabel !== '' ? channelLabel : (siteId !== '' ? siteId : ''); 2183 if (label === '') { 2184 return channelId; 2185 } 2186 2187 if (channelId !== '' && label.toLowerCase() !== channelId.toLowerCase()) { 2188 return label + ' (' + channelId + ')'; 2189 } 2190 2191 return label; 2192 } 2193 2194 function updateScopeText() { 2195 if (!global.document || typeof global.document.querySelectorAll !== 'function') { 2196 return; 2197 } 2198 2199 var label = resolveScopeLabel(); 2200 var scopeNodes = global.document.querySelectorAll('[data-icm-scope-text]'); 2201 for (var i = 0; i < scopeNodes.length; i++) { 2202 var node = scopeNodes[i]; 2203 if (label === '') { 2204 node.setAttribute('hidden', 'hidden'); 2205 node.textContent = ''; 2206 continue; 2207 } 2208 2209 node.removeAttribute('hidden'); 2210 node.textContent = 'Preferences apply to this website/channel: ' + label + '.'; 2211 } 2212 } 2213 2214 function setFeedbackMessage(message, tone) { 2215 if (!global.document || typeof global.document.querySelectorAll !== 'function') { 2216 return; 2217 } 2218 2219 var text = typeof message === 'string' ? message.trim() : ''; 2220 var feedbackNodes = global.document.querySelectorAll('[data-icm-feedback]'); 2221 for (var i = 0; i < feedbackNodes.length; i++) { 2222 var node = feedbackNodes[i]; 2223 if (text === '') { 2224 node.setAttribute('hidden', 'hidden'); 2225 node.textContent = ''; 2226 node.setAttribute('data-icm-feedback-tone', 'neutral'); 2227 continue; 2228 } 2229 2230 node.removeAttribute('hidden'); 2231 node.textContent = text; 2232 node.setAttribute('data-icm-feedback-tone', tone === 'error' ? 'error' : 'neutral'); 2233 } 2234 } 2235 2236 function updateRequestButtonsState(isPending) { 2237 if (!global.document || typeof global.document.querySelectorAll !== 'function') { 2238 return; 2239 } 2240 2241 var requestButtons = global.document.querySelectorAll('[data-icm-request-button]'); 2242 for (var i = 0; i < requestButtons.length; i++) { 2243 var button = requestButtons[i]; 2244 if (isPending) { 2245 button.setAttribute('disabled', 'disabled'); 2246 button.setAttribute('aria-busy', 'true'); 2247 button.classList.add('is-loading'); 2248 } else { 2249 button.removeAttribute('disabled'); 2250 button.removeAttribute('aria-busy'); 2251 button.classList.remove('is-loading'); 2252 } 2253 } 2254 } 2255 2256 function resolveRequestErrorMessage(error) { 2257 var code = ''; 2258 var errorPayload = error && error.payload && typeof error.payload === 'object' ? error.payload : {}; 2259 var errorName = typeof errorPayload.error === 'string' ? errorPayload.error : ''; 2260 if (error && typeof error.message === 'string') { 2261 code = error.message; 2262 } 2263 2264 if (code === 'http_409') { 2265 if (errorName === 'missing_visitor_cookie' || errorName === 'invali
2265d_visitor_context') { 2266 return 'Consent could not be saved because the browser session expired. Please try again.'; 2267 } 2268 return 'Consent could not be saved: no active policy version is configured.'; 2269 } 2270 if (code === 'http_422') { 2271 return 'Consent could not be saved: invalid decision payload.'; 2272 } 2273 if (code === 'fetch_not_available') { 2274 return 'Consent could not be saved in this browser (fetch unavailable).'; 2275 } 2276 if (code.indexOf('http_') === 0) { 2277 return 'Consent could not be saved due to a server error (' + code.replace('http_', '') + ').'; 2278 } 2279 2280 return 'Consent could not be saved right now. Please try again.'; 2281 } 2282 2283 function maybeReloadAfterConsentChange() { 2284 if (!behaviorSettings.reloadPageOnConsentChange) { 2285 return; 2286 } 2287 if (!global.location || typeof global.location.reload !== 'function') { 2288 return; 2289 } 2290 2291 if (typeof global.setTimeout === 'function') { 2292 global.setTimeout(function () { 2293 global.location.reload(); 2294 }, 100); 2295 return; 2296 } 2297 2298 global.location.reload(); 2299 } 2300 2301 function getFocusableElements(container) { 2302 if (!container || typeof container.querySelectorAll !== 'function') { 2303 return []; 2304 } 2305 2306 var selector = [ 2307 'a[href]', 2308 'button:not([disabled])', 2309 'textarea:not([disabled])', 2310 'input:not([disabled])', 2311 'select:not([disabled])', 2312 '[tabindex]:not([tabindex="-1"])' 2313 ].join(','); 2314 2315 var nodes = Array.prototype.slice.call(container.querySelectorAll(selector)); 2316 return nodes.filter(function (node) { 2317 return !node.hasAttribute('hidden') && node.getAttribute('aria-hidden') !== 'true'; 2318 }); 2319 } 2320 2321 function trapFocusInContainer(event, container) { 2322 if (!event || event.key !== 'Tab' || !container) { 2323 return; 2324 } 2325 2326 var focusables = getFocusableElements(container); 2327 if (focusables.length === 0) { 2328 event.preventDefault(); 2329 if (typeof container.focus === 'function') { 2330 container.focus(); 2331 } 2332 return; 2333 } 2334 2335 var first = focusables[0]; 2336 var last = focusables[focusables.length - 1]; 2337 var active = global.document ? global.document.activeElement : null; 2338 2339 if (event.shiftKey && active === first) { 2340 event.preventDefault(); 2341 last.focus(); 2342 return; 2343 } 2344 2345 if (!event.shiftKey && active === last) { 2346 event.preventDefault(); 2347 first.focus(); 2348 } 2349 } 2350 2351 function renderConsentUi() { 2352 if (!uiReady) { 2353 return; 2354 } 2355 2356 var banner = ensureConsentBanner(); 2357 var modal = ensurePreferencesModal(); 2358 var quickPanel = ensureQuickPanel(); 2359 var manageButton = ensureManageButton(); 2360 var bannerOverlay = ui.bannerOverlay; 2361 2362 if (!banner) { 2363 return; 2364 } 2365 2366 applyBannerVisualSettings(banner); 2367 applyQuickPanelVisualSettings(quickPanel); 2368 applyManageButtonVisualSettings(manageButton); 2369 applyModalVisualSettings(modal); 2370 updateScopeText(); 2371 updateRequestButtonsState(currentState.pendingRequest); 2372 2373 var shouldShowPrompt = !currentState.persistedDecision && !bannerDismissed; 2374 var modalOpen = !!(modal && !modal.hasAttribute('hidden')); 2375 var showPrimaryQuickPanel = shouldShowPrompt && shouldUseQuickPanelAsPrimary() && !modalOpen; 2376 var quickPanelActive = (quickPanelVisible || showPrimaryQuickPanel) && !modalOpen; 2377 var bannerVisible = shouldShowPrompt && !quickPanelActive && !modalOpen; 2378 2379 if (currentState.persistedDecision) { 2380 banner.setAttribute('hidden', 'hidden'); 2381 closePreferencesModal(true); 2382 modalOpen = false; 2383 if (manageButton && !quickPanelActive && !modalOpen) { 2384 manageButton.removeAttribute('hidden'); 2385 } else if (manageButton) { 2386 manageButton.setAttribute('hidden', 'hidden'); 2387 } 2388 } else if (bannerVisible) { 2389 banner.removeAttribute('hidden'); 2390 if (manageButton) { 2391 manageButton.setAttribute('hidden', 'hidden'); 2392 } 2393 } else { 2394 banner.setAttribute('hidden', 'hidden'); 2395 if (manageButton && !quickPanelActive && !modalOpen) { 2396 manageButton.removeAttribute('hidden'); 2397 } else if (manageButton) { 2398 manageButton.setAttribute('hidden', 'hidden'); 2399 } 2400 } 2401 2402 if (quickPanel) { 2403 if (quickPanelActive) { 2404 renderQuickPanelOptions(quickPanel); 2405 if (!quickPanel.__icmInitialCategories || showPrimaryQuickPanel) { 2406 quickPanel.__icmInitialCategories = normalizeCategories(currentState.categories); 2407 } 2408 syncQuickPanelPrimaryAction(quickPanel); 2409 quickPanel.removeAttribute('hidden'); 2410 } else { 2411 quickPanel.setAttribute('hidden', 'hidden'); 2412 } 2413 } 2414 2415 if (bannerOverlay) { 2416 var shouldShowOverlay = designSettings.fullPageOverlay && (bannerVisible || quickPanelActive); 2417 if (shouldShowOverlay) { 2418 bannerOverlay.removeAttribute('hidden'); 2419 } else { 2420 bannerOverlay.setAttribute('hidden', 'hidden'); 2421 } 2422 } 2423 } 2424 2425 global.IntegratedConsentManager = { 2426 getState: function () { 2427 return getStateSnapshot(); 2428 }, 2429 updateConsent: function (categoriesUpdate, options) { 2430 var opts = options && typeof options === 'object' ? options : {}; 2431 var persistedDecision = opts.persistedDecision !== false; 2432 return applyState(categoriesUpdate, persistedDecision, opts.source || 'runtime:manual_update', { 2433 persistSnapshot: opts.persistSnapshot !== false, 2434 markVerified: opts.markVerified === true 2435 }); 2436 }, 2437 syncState: function () { 2438 return syncStateFromApi(); 2439 }, 2440 persistDecision: function (categoriesUpdate, options) { 2441 var opts = options && typeof options === 'object' ? options : {}; 2442 var source = opts.source || 'runtime:persist_decision'; 2443 var nextCategories = normalizeCategories(categoriesUpdate); 2444 var payload = buildDecisionPayload(nextCategories, source); 2445 var previousPersistedDecision = currentState.persistedDecision; 2446 2447 currentState.pendingRequest = true; 2448 setFeedbackMessage(translations.savingDecision, 'neutral'); 2449 applyState(nextCategories, previousPersistedDecision, source + ':local', { 2450 persistSnapshot: false 2451 }); 2452 2453 return postJson(decisionsEndpoint, payload).catch(function (error) { 2454 if (!isRecoverableVisitorContextError(error)) { 2455 throw error; 2456 } 2457 2458 return syncStateFromApi().then(function () { 2459 return postJson(decisionsEndpoint, buildDecisionPayload(nextCategories, source)); 2460 }); 2461 }).then(function () { 2462 currentState.pendingRequest = false;
2463 var snapshot = applyState(nextCategories, true, source + ':accepted', { 2464 markVerified: true 2465 }); 2466 setFeedbackMessage('', 'neutral'); 2467 maybeReloadAfterConsentChange(); 2468 return snapshot; 2469 }, function (error) { 2470 currentState.pendingRequest = false; 2471 renderConsentUi(); 2472 setFeedbackMessage(resolveRequestErrorMessage(error), 'error'); 2473 throw error; 2474 }); 2475 }, 2476 revoke: function (options) { 2477 var opts = options && typeof options === 'object' ? options : {}; 2478 var source = opts.source || 'runtime:revoke'; 2479 var revokedCategories = buildRejectOptionalState(); 2480 var payload = buildRevokePayload(); 2481 var previousPersistedDecision = currentState.persistedDecision; 2482 2483 currentState.pendingRequest = true; 2484 setFeedbackMessage(translations.savingDecision, 'neutral'); 2485 applyState(revokedCategories, previousPersistedDecision, source + ':local', { 2486 persistSnapshot: false 2487 }); 2488 2489 return postJson(revokeEndpoint, payload).catch(function (error) { 2490 if (!isRecoverableVisitorContextError(error)) { 2491 throw error; 2492 } 2493 2494 return syncStateFromApi().then(function () { 2495 return postJson(revokeEndpoint, buildRevokePayload()); 2496 }); 2497 }).then(function () { 2498 currentState.pendingRequest = false; 2499 var snapshot = applyState(revokedCategories, true, source + ':accepted', { 2500 markVerified: true 2501 }); 2502 setFeedbackMessage('', 'neutral'); 2503 maybeReloadAfterConsentChange(); 2504 return snapshot; 2505 }, function (error) { 2506 currentState.pendingRequest = false; 2507 renderConsentUi(); 2508 setFeedbackMessage(resolveRequestErrorMessage(error), 'error'); 2509 throw error; 2510 }); 2511 }, 2512 openPreferences: function () { 2513 bannerDismissed = false; 2514 openPreferencesModal(); 2515 return getStateSnapshot(); 2516 }, 2517 openQuickPanel: function () { 2518 openQuickPanel(); 2519 return getStateSnapshot(); 2520 } 2521 }; 2522 2523 var hydratedSnapshot = hydrateStateFromStorage(); 2524 activateBlockedScriptsForGrantedCategories(); 2525 registerStorageSyncListener(); 2526 if (uiReady) { 2527 renderConsentUi(); 2528 } 2529 2530 if (config.autoSync !== false && typeof global.fetch === 'function' && typeof global.Promise === 'function') { 2531 if (!shouldSyncStateOnStartup(hydratedSnapshot)) { 2532 uiReady = true; 2533 renderConsentUi(); 2534 return; 2535 } 2536 2537 global.IntegratedConsentManager.syncState().then(function () { 2538 uiReady = true; 2539 renderConsentUi(); 2540 }, function () { 2541 uiReady = true; 2542 renderConsentUi(); 2543 return null; 2544 }); 2545 } 2546})(window);
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.