PageSourceSearch

https://vismagazine.nl/bundles/integratedconsentmanager/js/icm-runtime.js?v=20260404-1

js vismagazine.nl collected 2026-10-07 21:57:28 UTC 97,437 bytes, 2,546 lines download raw bytes

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, '&amp;')
1334            .replace(/</g, '&lt;')
1335            .replace(/>/g, '&gt;')
1336            .replace(/"/g, '&quot;')
1337            .replace(/'/g, '&#039;');
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.