PageSourceSearch

https://sdmsoftware.com/wp-content/plugins/webtoffee-cookie-consent/lite/frontend/js/script.js?ver=3.5.3

js sdmsoftware.com collected 2026-10-02 14:40:26 UTC 99,249 bytes, 2,541 lines download raw bytes

1/**
2 * WordPress localize object mapped to a constant.
3 */
4const _wccStore = window._wccConfig;
5const _wccStyle = window._wccStyles;
6
7// Define currentLaw as a global variable
8let currentLaw =  window._wccConfig._lawSelected[0];
9
10// Iniitialize showBanner as true
11let showBanner = true;
12_wccStore._backupNodes = [];
13_wccStore._resetConsentID = false;
14_wccStore._bannerState = false;
15_wccStore._preferenceOriginTag = false;
16_wccStore._poweredByHidden = false;
17_wccStore._consentDetailsHidden = false;
18
19_wccStore._cookiePath = (function() {
20    try {
21        const currentPathname = window.location.pathname;
22        
23        if (_wccStore._publicURL) {
24            try {
25                const publicURL = new URL(_wccStore._publicURL);
26                const publicPathname = publicURL.pathname.replace(/\/+$/, '');
27                
28                if (publicPathname && publicPathname !== '/' && currentPathname.startsWith(publicPathname)) {
29                    return publicPathname + '/';
30                }
31            } catch (e) {
32                // Ignore parsing errors
33            }
34        }
35        
36        return '/';
37    } catch (e) {
38        console.warn('Failed to detect cookie path:', e);
39        return '/';
40    }
41})();
42
43window.webtoffee = window.webtoffee || {};
44const ref = window.webtoffee;
45ref._wccConsentStore = new Map();
46
47ref._wccGetCookieMap = function () {
48    const cookieMap = {};
49    try {
50        document.cookie.split(";
50").forEach((cookie) => {
51            const [key, value] = cookie.split("=");
52            if (!key) return;
53            const trimmedKey = key.trim();
54            
55            if (trimmedKey === 'wt_consent' && cookieMap[trimmedKey]) {
56                return;
57            }
58            
59            cookieMap[trimmedKey] = value;
60        });
61    } catch (error) { }
62    return cookieMap;
63};
64
65const currentCookieMap = ref._wccGetCookieMap();
66
67ref._wccGetFromStore = function (key) {
68    return ref._wccConsentStore.get(key) || "";
69};
70
71ref._wccSetInStore = function (key, value) {
72    ref._wccConsentStore.set(key, value);
73    const consentPath = _wccStore._enableConsentSharing ? '/' : _wccStore._cookiePath;
74    ref._wccConsentStore.set('cookie_path', consentPath);
75    
76    const cookieStringArray = Array.from(ref._wccConsentStore, ([k, v]) => `${k}:${v}`);
77    const scriptExpiry = _wccStore?._bannerConfig?.[currentLaw]?.settings?.consentExpiry || 365;
78    
79    ref._wccSetCookie("wt_consent", cookieStringArray.join(","), scriptExpiry, undefined, undefined, consentPath);
80};
81
82const webtoffeeConsentMap = (currentCookieMap["wt_consent"] || "")
83    .split(",")
84    .reduce((prev, curr) => {
85        if (!curr) return prev;
86        const [key, value] = curr.split(":");
87        prev[key] = value;
88        return prev;
89    }, {});
90
91const cookiePath = webtoffeeConsentMap['cookie_path'];
92const isCorrectPath = _wccStore._enableConsentSharing && currentCookieMap["wt_consent"]
93    ? true
94    : cookiePath
95        ? cookiePath === _wccStore._cookiePath
96        : _wccStore._cookiePath === '/' && currentCookieMap["wt_consent"];
97
98const consentKeys = ["consent", "action"].concat(_wccStore._categories.map(({ slug }) => slug));
99const consentValue = isCorrectPath ? (item) => webtoffeeConsentMap[item] || "" : () => "";
100
101consentKeys.forEach(item => ref._wccConsentStore.set(item, consentValue(item)));
102if (isCorrectPath) {
103    ref._wccConsentStore.set('cookie_path', _wccStore._enableConsentSharing ? '/' : _wccStore._cookiePath);
104}
105/**
106 * Get the value of cookie by it's name.
107 * 
108 * @param {string} name Name of the cookie
109 * @returns {string}
110 */
111ref._wccGetCookie = function (name) {
112    const cookies = document.cookie.split(';');
113    for (let i = 0; i < cookies.length; i++) {
114        const cookie = cookies[i].trim();
115        if (cookie.startsWith(name + '=')) {
116            return decodeURIComponent(cookie.substring(name.length + 1));
117        }
118    }
119    return null;
120}
121
122/**
123 * Set a cookie on document.cookie object.
124 * 
125 * @param {*} name Name of the cookie.
126 * @param {*} value Value to be set.
127 * @param {*} days Expiry in days.
128 * @param {*} domain Cookie domain.
129 * @param {*} ssl SSL enabled.
130 * @param {*} path Cookie path (defaults to detected path for multisite support).
131 */
132ref._wccSetCookie = function (name, value, days = 0, domain = _wccStore._rootDomain, ssl = _wccStore._ssl, path = _wccStore._cookiePath) {
133    const date = new Date();
134    const domainStr = domain ? `domain=${domain}` : '';
135    const sslStr = ssl ? `secure` : '';
136    const toSetTime = days === 0 ? 0 : date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
137    
138    document.cookie = `${name}=${value}; expires=${new Date(toSetTime).toUTCString()}; path=${path};${domainStr}; SameSite=Strict;${sslStr};`;
139}
140
141function _wccSetConsentID() {
142    const cookieyesID = ref._wccGetFromStore("consentid");
143    if (cookieyesID) return;
144    const consentID = ref._wccRandomString(32);
145    ref._wccSetInStore("consentid", consentID);
146    _wccStore._resetConsentID = true;
147}
148function _wccSetConsentTime() {
149    const consentTime = new Date().getTime(); // Get current time if consent time is not available
150    ref._wccSetInStore("consent_time", consentTime);
151}
152
153function _wccRenewConsent() {
154    ref._wccSetInStore("action", "");
155}
156
157_revisitWccConsent = function () {
158    const type = _wccGetType();
159    if (type === 'classic') {
160        _wccShowBanner();
161    }
162    _wccSetPreferenceAction();
163    _wccToggleRevisit();
164};
165/**
166 * Search an element by it's data-tag attribute
167 * 
168 * @param {string} tag data-tag of an element. 
169 * @returns {object}
170 */
171function _wccGetElementByTag(tag) {
172    const item = document.querySelector('[data-tag=' + tag + ']');
173    return item ? item : false;
174}
175
176function _wccEscapeRegex(literal) {
177    return literal.replace(/[.*+?^${}()[\]\\]/g, "\\$&");
178}
179
180/**
181 * Bind click event to banner elements.
182 * 
183 * @param {string} tag data-tag of the element
184 * @param {function} fn callback function
185 */
186function _wccAttachListener(selector, fn) {
187    const item = _wccFindElement(selector);
188    item && item.addEventListener("click", fn);
189}
190function _wccClassRemove() {
191    return _wccClassAction("remove", ...arguments);
192}
193
194function _wccClassToggle() {
195    return _wccClassAction("toggle", ...arguments);
196}
197
198function _wccClassAction(action, selector, className, forParent = true) {
199    const item = _wccFindElement(selector, forParent);
200    return item && item.classList[action](className);
201}
202
203function _wccFindElement(selector, forParent) {
204    let createdSelector = selector;
205    switch (true) {
206        case selector.startsWith("="):
207            createdSelector = `[data-tag="${selector.substring(1)}"]`;
208        default:
209            break;
210    }
211    const element = document.querySelector(createdSelector);
212    if (!element || (forParent && !element.parentElement)) return null;
213    return forParent ? element.parentElement : element;
214}
215/**
216 * Remove an element from the DOM.
217 * 
218 * @param {string} tag data-tag of the element.
219 */
220function _wccRemoveElement(tag) {
221    const item = _wccGetElementByTag(tag);
222    item && item.remove();
223}
224
225/**
226 * Remove styles by it's id.
227 */
228function _wccRemoveStyles() {
229    const item = document.getElementById('wcc-style-inline');
230    item && item.remove();
231}
232
233/**
234 * Generate a random string for logging purposes.
235 * 
236 * @param {integer} length Length of the string to be generated.
237 * @returns 
238 */
239ref._wccRandomString = function (length, allChars = true) {
240    const chars = `${allChars ? `0123456789` : ""
241        }ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghiklmnopqrstuvwxyz`;
242    const response = [];
243    for (let i = 0; i < length; i++)
244        response.push(chars[Math.floor(Math.random() * chars.length)]);
245    if (!allChars) return response.join("");
246    return btoa(response.join("")).replace(/\=+$/, "");
247}
248
249/**
250 * Remove banner if necessary.
251 */
252function _wccRemoveBanner() {
253    _wccHideBanner();
254    if (_wccStore._bannerConfig[currentLaw].config.revisitConsent.status === true) {
255        _wccShowRevisit();
256    }
257    if(_wccGetType() === 'popup') {
258        //Remove the overlay when consent updated
259        _wccHideOverLay();
260        //Remove wcc-popup-overflow class from <body>
261        if (document.body && document.body.classList.contains('wcc-popup-overflow')) {
262            document.body.classList.remove('wcc-popup-overflow');
263        }
264    }
265}
266
267/**
268 * Initialize the plugin front-end operations.
269 * 
270 * @returns {boolean}
271 */
272async function _wccInitOperations() {
273    await _wccSetBanner();
274    _wccAttachNoticeStyles();
275    _wccAttachShortCodeStyles();
276    _wccRenderBanner();
277    _wccSetShowMoreLess();
278    // Determine consent renewed time
279    let renew_date = _wccStore._renewConsent;
280    // Update the consent time based on the last modified time of "wt_consent" cookie
281    const consent_time = _wccGetCookieLastModified("wt_consent");
282    // Check whether consent renewed date greater than current time
283    if (!ref._wccGetFromStore("action") || _wccPreviewEnabled() || renew_date && renew_date > consent_time) {
284        _wccShowBanner();
285        _wccSetInitialState();
286        if (renew_date && renew_date > consent_time) {
287            _wccAcceptReject("reject");
288            _wccRenewConsent();
289        }
290    } else {
291        _wccRemoveBanner();
292    }
293}
294function _wccPreviewEnabled() {
295    let params = (new URL(document.location)).searchParams;
296    return params.get("wcc_preview") && params.get("wcc_preview") === 'true';
297}
298/**
299 * Sets up the cookie consent banner based on visitor's location and applicable privacy laws (GDPR/CCPA)
300 */
301async function _wccSetBanner() {
302    const bannerConfig = _wccStore._bannerConfig;
303    const availableLaws = Object.keys(bannerConfig);
304   
305    // Initialize variables
306    let country = '';
307    let is_eu = false;
308    let region_code = '';
309    // Get allowed regions for CCPA and GDPR.
310    let ccpaAllowedRegions = _wccStore._ccpaAllowedRegions;
311    let gdprAllowedRegions = _wccStore._gdprAllowedRegions;
312    
313
314    // Check if any law requires geolocation
315    const needsGeolocation = availableLaws.some(law => 
316        bannerConfig[law].settings.selectedRegion !== 'ALL'
317    );
318    
319    // Only fetch visitor data if geolocation is needed
320    if (needsGeolocation) {
321        const visitor = await _wccGetCountryFromVisitorData();
322        if (visitor) {
323            country = visitor.country;
324            is_eu = visitor.in_eu;
325            region_code = visitor.region_code;
326        }
327    }
328
329    if (availableLaws.length === 1) {
330        currentLaw = availableLaws[0];
331        let bannerEnabled = _wccBannerEnabled();
332        if (!bannerEnabled) {
333            showBanner = false;
334        }
335    } else {
336        // Multiple laws - determine which one applies
337        if (country === 'EU' || is_eu) {
338            currentLaw = 'GDPR';
339        } else if (country === 'US') {
340            currentLaw = 'CCPA';
341        } else {
342            // Default to first available law if no specific conditions met
343            currentLaw = availableLaws[0];
344        }
345    }
346
347    let selectedRegion = _wccSelectedRegion();
348    let bannerEnabled = _wccBannerEnabled();
349    
350    // Handle GDPR specific logic
351    if (currentLaw === 'GDPR') {
352        if (!bannerEnabled) {
353            showBanner = false;
354        } else if (selectedRegion !== 'ALL') {
355            if (Array.isArray(gdprAllowedRegions) && gdprAllowedRegions.length > 0) {
356                // Check if visitor's country is in allowed regions
357                showBanner = gdprAllowedRegions.includes(country);
358            } else {
359                // Fallback to original logic if gdprAllowedRegions is empty
360                showBanner = is_eu;
361            }
362        }
363    } 
364    // Handle CCPA specific logic
365    else if (currentLaw === 'CCPA') {
366        if (!bannerEnabled) {
367            showBanner = false;
368        } else if (selectedRegion !== 'ALL') {
369            if (Array.isArray(ccpaAllowedRegions) && ccpaAllowedRegions.length > 0) {
370                // Only proceed if visitor is from US
371                if (country === 'US') {
372                    // Check if visitor's region code is in allowed regions
373                    showBanner = ccpaAllowedRegions.includes(region_code);
374                } else {
375                    showBanner = false;
376                }
377            } else {
378                // Fallback to original logic if ccpaAllowedRegions is empty
379                showBanner = _wccIsVisitorInCalifornia(country, region_code);
380            }
381        }
382    }
383}
384/**
385 * Sets the initial state of the plugin.
386 */
387function _wccSetInitialState() {
388    const activeLaw = _wccGetLaw();
389    const ccpaCheckBoxValue = _wccFindCheckBoxValue();
390    // CCPA: opt-out checkbox checked = sale consent revoked ("no"); unchecked = "yes".
391    if (activeLaw === "ccpa") {
392        ref._wccSetInStore("consent", ccpaCheckBoxValue ? "no" : "yes");
393    } else {
394        ref._wccSetInStore("consent", "no");
395    }
396    const responseCategories = { accepted: [], rejected: [] };
397
398    for (const category of _wccStore._categories) {
399        let valueToSet = "yes";
400
401        if (showBanner) {
402            if (
403                (activeLaw === "gdpr" &&
404                    !category.isNecessary &&
405                    !category.defaultConsent.gdpr) ||
406                (activeLaw === "ccpa" &&
407                    ccpaCheckBoxValue &&
408                    !category.defaultConsent.ccpa)
409            ) {
410                valueToSet = "no";
411            }
412        }
413
414        if (valueToSet === "no") {
415            responseCategories.rejected.push(category.slug);
416        } else {
417            responseCategories.accepted.push(category.slug);
418        }
419
420        ref._wccSetInStore(`${category.slug}`, valueToSet);
421    }
422
423    _wccUnblock();
424    _wccSendConsentEvent(responseCategories);
425}
426
427/**
428 * Add a class based on the banner type and position. Eg: 'wcc-banner-top'
429 * 
430 * @returns {boolean}
431 */
432function _wccAddPositionClass() {
433    const notice = _wccGetElementByTag('notice');
434    if (!notice) return false;
435    const container = notice.closest('.wcc-consent-container');
436    if (!container) return false;
437    const type = _wccStore._bannerConfig[currentLaw].settings.type;
438    let position = _wccStore._bannerConfig[currentLaw].settings.position;
439    if (type === 'popup') {
440        position = 'center';
441        //Show overlay when popup visible
442        _wccShowOverLay();
443        //Add wcc-popup-overflow class to <body> to prevent scroll when layout type is popup
444        document.body.classList.add('wcc-popup-overflow');
445    }
446    const noticeClass = 'wcc' + '-' + type + '-' + position;
447    container.classList.add(noticeClass);
448    const revisitConsent = _wccGetElementByTag('revisit-consent');
449    if (!revisitConsent) return false;
450    const revisitPosition = `wcc-revisit-${_wccStore._bannerConfig[currentLaw].config.revisitConsent.position}`;
451    revisitConsent.classList.add(revisitPosition);
452}
453
454/**
455 * Initialize the plugin operations.
456 */
457async function _wccInit() {
458    try {
459        _wccInitOperations();
460        _wccRemoveAllDeadCookies();
461        _wccWatchBannerElement();
462        _wccApplyCustomPlaceholders();
463    } catch (err) {
464        console.error(err);
465    }
466}
467
468/**
469 * Domready event, alternative to jQuery(document).ready() function
470 * 
471 * @param {function} callback 
472 * @returns 
473 */
474function _wccDomReady(callback) {
475    if (typeof document === 'undefined') {
476        return;
477    }
478    if (document.readyState === 'complete' || /** DOMContentLoaded + Images/Styles/etc loaded, so we call directly. */
479        document.readyState === 'interactive' /** DOMContentLoaded fires at this point, so we call directly. */
480    ) {
481        return void callback();
482    } /** DOMContentLoaded has not fired yet, delay callback until then. */
483    document.addEventListener('DOMContentLoaded', callback);
484}
485
486/**
487 * Callback function to Domready event.
488 */
489_wccDomReady(async function () {
490    try {
491        await _wccInit();
492    } catch (err) {
493        console.error(err);
494    }
495});
496
497/**
498 * Register event handler for all the action elements.
499 */
500function _wccRegisterListeners() {
501    for (const { slug } of _wccStore._categories) {
502        _wccAttachListener('detail-category-title', () =>
503            document
504                .getElementById(`wccCategory${slug}`)
505                .classList.toggle("wcc-tab-active")
506        );
507    }
508    _wccAttachListener("=settings-button", () => _wccSetPreferenceAction('settings-button'));
509    _wccAttachListener("=detail-close", () => _wccHidePreferenceCenter());
510    _wccAttachListener("=optout-cancel-button", () => _wccHidePreferenceCenter());
511    _wccAttachListener("=close-button", () => _wccActionClose());
512    _wccAttachListener("=donotsell-button", () => _wccSetPreferenceAction('donotsell-button'));
513    _wccAttachListener("=reject-button", _wccAcceptReject("reject"));
514    _wccAttachListener("=accept-button", _wccAcceptReject("all"));
515    _wccAttachListener("=detail-accept-button", _wccAcceptReject("all"));
516    _wccAttachListener("=detail-save-button", _wccAcceptReject());
517    _wccAttachListener("=detail-category-preview-save-button", _wccAcceptReject());
518    _wccAttachListener("=optout-confirm-button", _wccAcceptReject());
519    _wccAttachListener("=detail-reject-button", _wccAcceptReject("reject"));
520    _wccAttachListener("=revisit-consent", () => _revisitWccConsent());
521    _wccAttachListener("=optout-close", () => _wccHidePreferenceCenter());
522    _wccAttachListener("=category-tab", () => _wccShowTab('wccIABSectionCookie'));
523    _wccAttachListener("=purpose-tab", () => _wccShowTab('wccIABSectionPurpose'));
524    _wccAttachListener("=vendor-tab", () => _wccShowTab('wccIABSectionVendor'));
525    _wccAttachListener('#wcc-iab-notice-toggle', () => _wccShowPreferenceCenter());
526    _wccInitiAccordionTabs();
527    _wccInitPreferenceButton();
528    _wccAttachCategoryListeners();
529
530}
531function _wccAttachCategoryListeners() {
532    if (!_wccStore._bannerConfig[currentLaw].config.auditTable.status) return;
533
534    const categoryNames = _wccStore._categories.map(({ slug }) => slug);
535    const sync = (root, expanded) => {
536        if (!root) return;
537        const btn = root.querySelector('.wcc-accordion-btn');
538        const body = root.querySelector('.wcc-accordion-body');
539        root.removeAttribute('aria-expanded');
540        if (btn) btn.setAttribute('aria-expanded', expanded ? 'true' : 'false');
541        if (body) body.setAttribute('aria-hidden', expanded ? 'false' : 'true');
542    };
543
544    categoryNames.map((category) => {
545        const selector = `#wccDetailCategory${category}`;
546        _wccAttachListener(selector, ({ target: { id } }) => {
547            if (id === `wccSwitch${category}`) return;
548            const accordion = document.querySelector(selector);
549            if (!accordion) return;
550            const isActive = accordion.classList.toggle('wcc-accordion-active');
551            sync(accordion, isActive);
552            categoryNames
553                .filter((categoryName) => categoryName !== category)
554                .map((filteredName) => {
555                    const other = document.querySelector(`#wccDetailCategory${filteredName}`);
556                    if (!other) return;
557                    other.classList.remove('wcc-accordion-active');
558                    sync(other, false);
559                });
560        });
561    });
562}
563/**
564 * Add support for accordion tabs on the privacy overview screen.
565 */
566function _wccInitiAccordionTabs() {
567    _wccIabInnerAccordionClickListener('wccIABSectionPurpose');
568    _wccIabInnerAccordionClickListener('wccIABSectionVendor');
569
570    document.querySelectorAll(".wcc-child-accordion .wcc-child-accordion-item").forEach((item) => (
571        item.addEventListener('click', function (event) {
572            if (event.target.type === 'checkbox') return;
573
574            const accordion = this.parentNode;
575            const btn = accordion.querySelector('.wcc-child-accordion-btn');
576            const body = accordion.querySelector('.wcc-child-accordion-body');
577            const isActive = accordion.classList.toggle('wcc-child-accordion-active');
578            accordion.removeAttribute('aria-expanded');
579            if (btn) btn.setAttribute('aria-expanded', isActive ? 'true' : 'false');
580            if (body) body.setAttribute('aria-hidden', isActive ? 'false' : 'true');
581        })
582    ));
583
584    // Enable / disable all purposes/features consent based on user action
585    const checkboxes = document.querySelectorAll('.wcc-consents-checkbox');
586    checkboxes.forEach(function (checkbox) {
587        checkbox.addEventListener('change', wcc_enable_disable_consent);
588    });
589}
590
591/**
592 * This function selects all elements with the class 'wcc-preference-btn', assigns a unique ID to each button,
593 * and adds a click event listener that triggers the `_revisitWccConsent` function.
594 * 
595 */
596function _wccInitPreferenceButton() {
597    const preferenceButtons = document.querySelectorAll('.wcc-preference-btn'); // Select all buttons with the class
598    preferenceButtons.forEach((button) => {
599        // Generate a unique ID for each preference button
600        const uniqueID = ref._wccRandomString(8, false);
601        // Set the unique ID
602        button.id = uniqueID;
603        // Add an event listener for each button
604        button.addEventListener('click', function () {
605            _revisitWccConsent();
606        });
607    });
608}
609
610function wcc_enable_disable_consent() {
611    let el = this.getAttribute('data-tag');
612    const nestedInputs = document.querySelectorAll('.wcc-' + el + '-checkbox, .wcc-' + el + '-legitimate-interests-checkbox');
613    const isChecked = this.checked;
614    nestedInputs.forEach(input => {
615        input.checked = isChecked;
616    });
617}
618function _wccIabInnerAccordionClickListener(containerId) {
619    const accordionItems = document.querySelectorAll(`#${containerId} .wcc-accordion .wcc-accordion-iab-item`);
620    accordionItems.forEach((item) => {
621        item.addEventListener('click', function (event) {
622            if (event.target.type === 'checkbox') return;
623            const accordion = this.parentNode;
624            const btn = this.querySelector('.wcc-accordion-btn');
625            const body = accordion.querySelector('.wcc-accordion-body');
626            accordion.classList.toggle('wcc-accordion-active');
627            const isActive = accordion.classList.toggle('wcc-child-accordion-active');
628            accordion.removeAttribute('aria-expanded');
629            if (btn) btn.setAttribute('aria-expanded', isActive ? 'true' : 'false');
630            if (body) body.setAttribute('aria-hidden', isActive ? 'false' : 'true');
631        });
632    });
633}
634
635function _wccToggleBanner(force = false) {
636    const notice = _wccGetElementByTag('notice');
637    const container = notice && notice.closest('.wcc-consent-container') || false;
638    if (container) {
639        force === true ? container.classList.add('wcc-hide') : container.classList.toggle('wcc-hide');
640    }
641}
642
643function _wccToggleRevisit(force = false) {
644    const revisit = _wccGetRevisit();
645    if (revisit) {
646        force === true ? _wccRevisitHide() : revisit.classList.toggle('wcc-revisit-hide');
647    }
648}
649function _wccGetLaw() {
650    return _wccStore._bannerConfig[currentLaw].settings.applicableLaw;
651}
652function _wccSelectedRegion() {
653    return _wccStore._bannerConfig[currentLaw].settings.selectedRegion;
654}
655function _wccBannerEnabled() {
656    return _wccStore._bannerConfig[currentLaw].settings.bannerEnabled;
657}
658function _wccPoweredByEnabled() {
659    return _wccStore._bannerConfig[currentLaw].settings.poweredBy;
660}
661function _wccGetType() {
662    return _wccStore._bannerConfig[currentLaw].settings.type;
663}
664function _wccGetBanner() {
665    const notice = _wccGetElementByTag('notice');
666    const container = notice && notice.closest('.wcc-consent-container') || false;
667    return container && container || false;
668}
669function _wccHideBanner() {
670    const notice = _wccGetBanner();
671    notice && notice.classList.add('wcc-hide');
672}
673function _wccShowBanner() {
674    const notice = _wccGetBanner();
675    notice && notice.classList.remove('wcc-hide');
676}
677function _wccHideOverLay() {
678    const overlay = document.querySelector('.wcc-overlay');
679    overlay && overlay.classList.add('wcc-hide');
680}
681function _wccShowOverLay() {
682    const overlay = document.querySelector('.wcc-overlay');
683    overlay && overlay.classList.remove('wcc-hide');
684}
685function _wccToggleOverLay() {
686    const overlay = document.querySelector('.wcc-overlay');
687    overlay && overlay.classList.toggle('wcc-hide');
688}
689function _wccGetPreferenceCenter() {
690    if (_wccGetType() === 'classic') {
691        return _wccGetBanner();
692    }
693    let element = _wccGetLaw() === 'ccpa' ? _wccGetElementByTag("optout-popup") : _wccGetElementByTag("detail");
694    return element && element.closest('.wcc-modal') || false;
695}
696function _wccHidePreferenceCenter() {
697    const element = _wccGetPreferenceCenter();
698    element && element.classList.remove(_wccGetPreferenceClass());
699    if (_wccGetType() !== 'classic') {
700        _wccHideOverLay();
701        if (!ref._wccGetFromStore("action")) _wccShowBanner();
702    }
703    if (ref._wccGetFromStore("action")) _wccShowRevisit();
704    const origin = _wccStore._preferenceOriginTag;
705    origin && _wccSetFocus(origin)
706}
707function _wccShowPreferenceCenter() {
708    const element = _wccGetPreferenceCenter();
709    element && element.classList.add(_wccGetPreferenceClass());
710    if (_wccGetType() !== 'classic') {
711        _wccShowOverLay();
712        _wccHideBanner();
713    }
714}
715function _wccTogglePreferenceCenter() {
716    const element = _wccGetPreferenceCenter();
717    element && element.classList.toggle(_wccGetPreferenceClass());
718    if (_wccGetType() !== 'classic') _wccToggleOverLay();
719}
720function _wccGetPreferenceClass() {
721    return _wccGetType() === 'classic' ? 'wcc-consent-bar-expand' : 'wcc-modal-open';
722}
723function _wccGetRevisit() {
724    const revisit = _wccGetElementByTag('revisit-consent');
725    return revisit && revisit || false;
726}
727function _wccHideRevisit() {
728    const revisit = _wccGetRevisit();
729    revisit && revisit.classList.add('wcc-revisit-hide')
730}
731function _wccShowRevisit() {
732    const revisit = _wccGetRevisit();
733    revisit && revisit.classList.remove('wcc-revisit-hide')
734}
735function _wccSetPreferenceAction(tagName = false) {
736    _wccStore._preferenceOriginTag = tagName;
737    if (_wccGetType() === 'classic') {
738        _wccTogglePreferenceCenter();
739    } else {
740        _wccShowPreferenceCenter();
741    }
742}
743// Define section names array
744const sectionNames = ['wccIABSectionCookie', 'wccIABSectionPurpose', 'wccIABSectionVendor'];
745function _wccShowTab(tagName = false) {
746    sectionNames.forEach(validTagName => {
747        _wccNavBar();
748        const tab = _wccGetTab(validTagName);
749        if (validTagName === tagName) {
750            tab && tab.classList.remove('wcc-hide');
751        } else {
752            tab && tab.classList.add('wcc-hide');
753        }
754    });
755}
756function _wccNavBar() {
757    // Get all buttons
758    const buttons = document.querySelectorAll('.wcc-iab-nav-item');
759
760    // Add click event listeners to each button
761    buttons.forEach(button => {
762        button.addEventListener('click', function () {
763            // Remove 'wcc-iab-nav-item-active' from all buttons
764            buttons.forEach(btn => btn.classList.remove('wcc-iab-nav-item-active'));
765
766            // Add 'wcc-iab-nav-item-active' only to the clicked button
767            this.classList.add('wcc-iab-nav-item-active');
768        });
769    });
770}
771function _wccGetTab(tagName = false) {
772    const tab = _wccGetElementByTag(tagName);
773    return tab && tab || false;
774}
775/**
776 * Replace footer shadow with current preference center background.
777 * 
778 * @param {object} $doc Dom node.
779 * @returns 
780 */
781function _wccSetFooterShadow($doc) {
782    const footer = $doc.querySelector('[data-tag="detail"] .wcc-footer-shadow');
783    const preference = $doc.querySelector('[data-tag="detail"]');
784    if (!footer) return;
785    const background = preference && preference.style.backgroundColor || '#ffffff';
786    footer.style.background = `linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, ${background
787        } 100%)`;
788}
789
790/**
791 * Remove all the rejected cookies.
792 * 
793 * @param {object} cookies Cookies list.
794 */
795function _wccRemoveDeadCookies({ cookies }) {
796    const currentCookieMap = ref._wccGetCookieMap();
797    for (const { cookieID, domain } of cookies) {
798        for (const existingCookieID in currentCookieMap) {
799            // Check if the cookieID matches exactly or if it matches a wildcard pattern
800            const isMatch = 
801                existingCookieID === cookieID || 
802                (cookieID.includes('*') && new RegExp(`^${cookieID.replace('*', '.*')}$`).test(existingCookieID));
803            
804            if (isMatch) {
805                [domain, ""].map((cookieDomain) =>
806                    ref._wccSetCookie(existingCookieID, "", 0, cookieDomain)
807                );
808            }
809        }
810    }
811}
812function _wccSetPreferenceCheckBoxStates(revisit = false) {
813    for (const category of _wccStore._categories) {
814        const cookieValue = ref._wccGetFromStore(category.slug);
815        const checked =
816            cookieValue === "yes" ||
817            (!cookieValue &&
818                category.defaultConsent[_wccGetLaw()]) || category.isNecessary;
819
820        const disabled = category.isNecessary;
821        const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
822            (code) => code.key === 'wcc_category_toggle_label'
823        );
824        const toggleTextFormatted = shortCodeData.content.replace(
825            `[wcc_preference_{{category_slug}}_title]`,
826            category.name
827        );
828        _wccSetCheckboxes(
829            category,
830            checked,
831            disabled,
832            toggleTextFormatted,
833            revisit
834        );
835        _wccSetPreferenceState(category);
836    }
837}
838
839function _wccSetCheckboxes(
840    category,
841    checked,
842    disabled,
843    formattedLabel,
844    revisit = false
845) {
846    const toggle = _wccStore._bannerConfig[currentLaw].config.preferenceCenter.toggle;
847    const activeColor = toggle.states.active.styles['background-color'];
848    const inactiveColor = toggle.states.inactive.styles['background-color'];
849
850    [`wccCategoryDirect`, `wccSwitch`].map((key) => {
851        const boxElem = document.getElementById(`${key}${category.slug}`);
852        if (!boxElem) return;
853        _wccSetCategoryToggle(
854            boxElem,
855            category,
856            revisit);
857        boxElem.checked = checked;
858        boxElem.disabled = disabled;
859        boxElem.style.backgroundColor = checked ? activeColor : inactiveColor;
860        _wccSetCheckBoxAriaLabel(boxElem, checked, formattedLabel);
861        if (revisit) return;
862        boxElem.addEventListener("change", ({ currentTarget: elem }) => {
863            const isChecked = elem.checked;
864            elem.style.backgroundColor = isChecked ? activeColor : inactiveColor;
865            _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel);
866        });
867    });
868}
869function _wccSetCategoryToggle(element, category = {}, revisit = false) {
870    if (revisit) return;
871    if (element.parentElement.getAttribute('data-tag') === 'detail-category-toggle') {
872        _wccSetCategoryPreferenceToggle(element, category);
873    } else if (element.parentElement.getAttribute('data-tag') === 'detail-category-preview-toggle') {
874        _wccSetCategoryPreview(element, category);
875    }
876
877}
878function _wccSetCategoryPreferenceToggle(element, category) {
879    let toggleContainer = element.closest('.wcc-accordion-item');
880    if (!toggleContainer) return;
881    const toggleSwitch = toggleContainer.querySelector('.wcc-switch');
882    const necessaryText = toggleContainer.querySelector('.wcc-always-active');
883    if (category.isNecessary) {
884        toggleSwitch && toggleSwitch.remove();
885    } else {
886        necessaryText && necessaryText.remove();
887        if (_wccGetType() === 'classic' && _wccStore._bannerConfig[currentLaw].config.categoryPreview.status || (category.cookies && category.cookies.length === 0) && !category.foundNoCookieScript) {
888            toggleSwitch && toggleSwitch.remove();
889        }
890    }
891}
892function _wccSetPreferenceState(category) {
893    if (_wccStore._bannerConfig[currentLaw].config.auditTable.status === false) {
894        const tableElement = document.querySelector(
895            `#wccDetailCategory${category.slug} [data-tag="audit-table"]`
896        );
897        tableElement && tableElement.remove();
898        const chevronElement = document.querySelector(
899            `#wccDetailCategory${category.slug} .wcc-accordion-chevron`
900        );
901        chevronElement && chevronElement.classList.add("wcc-accordion-chevron-hide");
902    }
903}
904function _wccSetCategoryPreview(element, category) {
905    if ((category.cookies && category.cookies.length === 0) && !category.isNecessary)
906        element.parentElement.parentElement.remove();
907}
908
909function _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel, isCCPA = false) {
910
911    if (!boxElem) return;
912    const keyName = isChecked ? "disable" : "enable";
913    const textCode = `wcc_${keyName}_${isCCPA ? "optout" : "category"}_label`;
914    const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
915        (code) => code.key === textCode
916    );
917    if (!shortCodeData) return;
918    const labelText = formattedLabel
919        .replace(/{{status}}/g, keyName)
920        .replace(`[${textCode}]`, shortCodeData.content);
921    boxElem.setAttribute("aria-label", labelText);
922}
923/**
924 * Render banner after processing.
925 */
926async function _wccRenderBanner() {
927    const myEvent = new CustomEvent("_wccBannerVisible", {
928        detail: {
929            bannerVisibility: showBanner, // Include showBanner in the detail property
930            currentLaw: currentLaw,
931        },
932        bubbles: true,
933        cancelable: true,
934        composed: false,
935    });
936    // Ensure the event listener is added before dispatching the event
937    setTimeout(() => {
938        document.body.dispatchEvent(myEvent);
939    }, 0);
940
941    if (currentLaw && showBanner) {
942        const template = document.getElementById(`wccBannerTemplate_${currentLaw}`);
943        if (template) {
944            const templateHtml = template.innerHTML;
945            const doc = new DOMParser().parseFromString(templateHtml, 'text/html');
946            _wccSetFooterShadow(doc);
947            document.body.insertAdjacentHTML(
948                "afterbegin",
949                doc.body.innerHTML
950            );
951        }
952    }
953    _wccSetPreferenceCheckBoxStates();
954    _wccRegisterListeners();
955    _wccSetCCPAOptions();
956    _wccSetNoticeGpcOption();
957    _wccSetPlaceHolder();
958    _wccAttachReadMore();
959    _wccRemoveStyles();
960    _wccAddPositionClass();
961    _wccAddRtlClass();
962    _wccSetPoweredBy();
963    _wccSetConsentDetails();
964    _wccSetFooterWrapperStyles();
965}
966
967async function _wccGetCountryFromVisitorData() {
968    const apiUrl = _wccStore._restApiUrl;
969    const response = await fetch(apiUrl);
970    const data = await response.json();
971    return data;
972}
973
974/**
975 * Check whether the visitor country is US and If _restrictToCA is true check the region code also.
976 * @since 3.0.2
977 * @returns boolean
978 */
979function _wccIsVisitorInCalifornia(country, region_code) {
980    const _restricttocalifornia = _wccStore._restrictToCA;
981    // Check if restriction to California is enabled
982    if (_restricttocalifornia) {
983        // Restrict to California only
984        return country === 'US' && region_code === 'CA';
985    } else {
986        // Only check if country is US
987        return country === 'US';
988    }
989}
990
991/**
992 * Accept or reject the consent based on the option.
993 * 
994 * @param {string} option Type of consent. 
995 * @returns {void}
996 */
997function _wccAcceptReject(option = "custom") {
998    return () => {
999        _wccAcceptCookies(option);
1000        _wccSetConsentID();
1001        _wccSetConsentTime();
1002        _wccRemoveBanner();
1003        _wccHidePreferenceCenter();
1004        _wccLogConsent();
1005        _wccAfterConsent();
1006    };
1007}
1008
1009function _wccActionClose() {
1010    const actionType = _wccStore._bannerConfig[currentLaw].settings.closeButtonAction;
1011    _wccAcceptReject(actionType)();
1012}
1013/**
1014 * Consent accept callback.
1015 * 
1016 * @param {string} choice  Type of consent.
1017 */
1018function _wccAcceptCookies(choice = "all") {
1019    const activeLaw = _wccGetLaw();
1020    const ccpaCheckBoxValue = _wccFindCheckBoxValue();
1021
1022    ref._wccSetInStore("action", "yes");
1023    if (activeLaw === 'gdpr') {
1024        ref._wccSetInStore("consent", choice === "reject" ? "no" : "yes");
1025    } else {
1026        ref._wccSetInStore("consent", ccpaCheckBoxValue ? "no" : "yes");
1027    }
1028    const responseCategories = { accepted: [], rejected: [] };
1029    for (const category of _wccStore._categories) {
1030        let valueToSet = "no";
1031        if (activeLaw === 'gdpr') {
1032            valueToSet =
1033                !category.isNecessary &&
1034                    (choice === "reject" ||
1035                        (choice === "custom" && !_wccFindCheckBoxValue(category.slug)))
1036                    ? "no"
1037                    : "yes";
1038        } else {
1039            valueToSet = (ccpaCheckBoxValue && !category.defaultConsent.ccpa) || (!category.isNecessary && currentLaw === 'CCPA' && choice === "reject") ? "no" : "yes";
1040        }
1041        ref._wccSetInStore(`${category.slug}`, valueToSet);
1042        if (valueToSet === "no") {
1043            responseCategories.rejected.push(category.slug);
1044            _wccRemoveDeadCookies(category);
1045        } else responseCategories.accepted.push(category.slug);
1046    }
1047    let customEventSupport = _wccStore._customEvents;
1048    // Add GTM event trigger after consent mode flags are set
1049    if (window.dataLayer && customEventSupport) {
1050        window.dataLayer.push({
1051            "event": "cookie_consent_updated"
1052        });
1053    }
1054    _wccUnblock();
1055    _wccReblockExistingEmbeds();
1056    _wccApplyCustomPlaceholders();
1057    _wccSendConsentEvent(responseCategories,choice);
1058}
1059function _wccSetShowMoreLess() {
1060    const activeLaw = _wccGetLaw();
1061    const showCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
1062        (code) => code.key === "wcc_show_desc"
1063    );
1064    const hideCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
1065        (code) => code.key === "wcc_hide_desc"
1066    );
1067
1068    if (!showCode || !hideCode) return;
1069    const hideButtonContent = hideCode.content;
1070    const showButtonContent = showCode.content;
1071
1072    const contentLimit = window.innerWidth < 376 ? 150 : 300;
1073    const element = document.querySelector(
1074        `[data-tag="${activeLaw === "gdpr" ? "detail" : "optout"}-description"]`
1075    );
1076    if (!element) return;
1077    const content = element.textContent;
1078    if (content.length < contentLimit) return;
1079    const contentHTML = element.innerHTML;
1080    const htmlDoc = new DOMParser().parseFromString(contentHTML, "text/html");
1081    const innerElements = htmlDoc.querySelectorAll("body > p");
1082    if (innerElements.length <= 1) return;
1083    let strippedContent = ``;
1084    for (let index = 0; index < innerElements.length; index++) {
1085        if (index === innerElements.length - 1) return;
1086        const element = innerElements[index];
1087        if (`${strippedContent}${element.outerHTML}`.length > contentLimit)
1088            element.insertAdjacentHTML("beforeend", `...&nbsp;${showButtonContent}`);
1089        strippedContent = `${strippedContent}${element.outerHTML}`;
1090        if (strippedContent.length > contentLimit) break;
1091    }
1092    function showMoreHandler() {
1093        element.innerHTML = `${contentHTML}${hideButtonContent}`;
1094        _wccAttachListener("=hide-desc-button", showLessHandler);
1095    }
1096    function showLessHandler() {
1097        element.innerHTML = strippedContent;
1098        _wccAttachListener("=show-desc-button", showMoreHandler);
1099    }
1100    showLessHandler();
1101}
1102/**
1103 * Toggle show more or less on click event.
1104 * 
1105 * @param {object} object Object containing toggle buttons and texts.
1106 * @param {*} element Target element.
1107 */
1108function _wccToggleMoreLess(object, element) {
1109    let {
1110        currentTarget,
1111        target
1112    } = element;
1113    if (target && target.tagName.toUpperCase() !== 'BUTTON') return;
1114    const ariaExpanded = currentTarget.getAttribute('aria-expanded');
1115    const trimmed = ariaExpanded === 'false';
1116    let btn = object.btnTrim;
1117    let text = object.originalText;
1118    if (!trimmed) {
1119        btn = object.btnExpand;
1120        text = object.truncatedText;
1121    }
1122    currentTarget.innerHTML = `${text}${btn}`;
1123    currentTarget.ariaExpanded = trimmed;
1124}
1125
1126/**
1127 * Add styles to the shortcode HTML rendered outside of the banner.
1128 * 
1129 * @returns {void}
1130 */
1131function _wccAttachShortCodeStyles() {
1132    const shortCodes = _wccStore._tags;
1133    Array.prototype.forEach.call(shortCodes, function (shortcode) {
1134        document.querySelectorAll('[data-tag=' + shortcode.tag + ']').forEach(function (item) {
1135            let styles = '';
1136            for (const key in shortcode.styles) {
1137                styles += `${key}: ${shortcode.styles[key]};`;
1138            }
1139            item.style.cssText = styles;
1140        });
1141    });
1142}
1143
1144/** Script blocker Version 2 */
1145
1146
1147const _wccCreateElementBackup = document.createElement;
1148
1149document.createElement = (...args) => {
1150    const createdElement = _wccCreateElementBackup.call(document, ...args);
1151
1152    if (createdElement.nodeName.toLowerCase() !== "script") return createdElement;
1153
1154    const originalSetAttribute = createdElement.setAttribute.bind(createdElement);
1155
1156    // Get the native src descriptor from HTMLScriptElement prototype
1157    const nativeSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src');
1158
1159    Object.defineProperties(createdElement, {
1160        src: {
1161            configurable: true,
1162            enumerable: true,
1163            get: function () {
1164                // Use native getter for accurate absolute URL
1165                return nativeSrcDescriptor.get.call(createdElement);
1166            },
1167            set: function (value) {
1168                if (typeof _wccStore !== 'undefined' && _wccShouldChangeType(createdElement, value))
1169                    originalSetAttribute("type", "javascript/blocked");
1170                originalSetAttribute("src", value);
1171                return true;
1172            },
1173        },
1174        type: {
1175            configurable: true,
1176            enumerable: true,
1177            get: function () {
1178                return createdElement.getAttribute("type");
1179            },
1180            set: function (value) {
1181                value = (typeof _wccStore !== 'undefined' && _wccShouldChangeType(createdElement))
1182                    ? "javascript/blocked"
1183                    : value;
1184                originalSetAttribute("type", value);
1185                return true;
1186            },
1187        },
1188    });
1189
1190    createdElement.setAttribute = (name, value) => {
1191        if (name === "type" || name === "src")
1192            return (createdElement[name] = value);
1193        originalSetAttribute(name, value);
1194        if (name === "data-wcc" && !_wccShouldChangeType(createdElement))
1195            originalSetAttribute("type", "text/javascript");
1196    };
1197
1198    return createdElement;
1199};
1200
1201function _wccMutationObserver(mutations) {
1202    const urlParams = new URLSearchParams(window.location.search);
1203    if (urlParams.has('wcc_bypass')) return;
1204
1205    for (const { addedNodes } of mutations) {
1206        for (const node of addedNodes) {
1207            try {
1208                // Handle TikTok blockquote elements.
1209                if (node.nodeName === 'BLOCKQUOTE' && node.classList.contains('tiktok-embed')) {
1210                    const videoId = node.getAttribute('data-video-id');
1211                    if (!videoId) continue;
1212
1213                    const uniqueID = ref._wccRandomString(8, false);
1214                    const pseudoIframe = {
1215                        nodeName: 'iframe',
1216                        src: `https://www.tiktok.com/embed/${videoId}`,
1217                        offsetWidth: parseInt(node.style.maxWidth) || 605,
1218                        offsetHeight: parseInt(node.style.minHeight) || 700,
1219                        insertAdjacentHTML: function (position, html) {
1220                            node.insertAdjacentHTML(position, html);
1221                        }
1222                    };
1223
1224                    const cleanedHostname = _wccCleanHostName('tiktok.com');
1225                    _wccAddProviderToList(node, cleanedHostname);
1226                    
1227                    // Check if element has data-wcc attribute that should prevent blocking.
1228                    if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) {
1229                        continue;
1230                    }
1231                    // Check if provider should be blocked.
1232                    if (!_wccShouldBlockProvider(cleanedHostname)) continue;
1233
1234                    const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname);
1235                    _wccAddPlaceholder(pseudoIframe, uniqueID, category);
1236
1237                    const tiktokScript = document.querySelector('script[src*="tiktok.com/embed.js"]');
1238                    if (tiktokScript) tiktokScript.remove();
1239
1240                    node.remove();
1241                    _wccStore._backupNodes.push({
1242                        position: 'body',
1243                        node: node.cloneNode(true),
1244                        uniqueID
1245                    });
1246                    continue;
1247                }
1248
1249                //Handle Elementor video widget (YouTube, Vimeo, Dailymotion, VideoPress, Self Hosted).
1250                if (node.nodeType === 1 && node.classList.contains('elementor-video')) {
1251                    const widgetContainer = node.closest('.elementor-widget-video');
1252                    if (!widgetContainer) continue;
1253
1254                    const settings = widgetContainer.getAttribute('data-settings');
1255                    if (!settings) continue;
1256
1257                    try {
1258                        const parsed = JSON.parse(settings.replace(/&quot;/g, '"'));
1259                        const videoType = parsed.video_type || 'youtube';
1260                        
1261                        let videoUrl = '';
1262                        let hostname = '';
1263                        let videoId = '';
1264                        let embedUrl = '';
1265                        let externalUrlEnabled = false;
1266                        
1267                        // Handle different video types.
1268                        switch (videoType) {
1269                            case 'youtube':
1270                                videoUrl = parsed.youtube_url;
1271                                if (!videoUrl || typeof videoUrl !== 'string') continue;
1272                                const ytMatch = videoUrl.match(/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([\w-]{11})/);
1273                                if (!ytMatch) continue;
1274                                videoId = ytMatch[1];
1275                                hostname = 'youtube.com';
1276                                embedUrl = `https://www.youtube.com/embed/${videoId}`;
1277                                break;
1278                                
1279                            case 'vimeo':
1280                                videoUrl = parsed.vimeo_url;
1281                                if (!videoUrl || typeof videoUrl !== 'string') continue;
1282                                const vimeoMatch = videoUrl.match(/(?:vimeo\.com\/)(\d+)/);
1283                                if (!vimeoMatch) continue;
1284                                videoId = vimeoMatch[1];
1285                                hostname = 'vimeo.com';
1286                                embedUrl = `https://player.vimeo.com/video/${videoId}`;
1287                                break;
1288                                
1289                            case 'dailymotion':
1290                                videoUrl = parsed.dailymotion_url;
1291                                if (!videoUrl || typeof videoUrl !== 'string') continue;
1292                                const dmMatch = videoUrl.match(/(?:dailymotion\.com\/video\/)([a-zA-Z0-9]+)/);
1293                                if (!dmMatch) continue;
1294                                videoId = dmMatch[1];
1295                                hostname = 'dailymotion.com';
1296                                embedUrl = `https://www.dailymotion.com/embed/video/${videoId}`;
1297                                break;
1298                                
1299                            case 'videopress':
1300                                videoUrl = parsed.videopress_url;
1301                                if (!videoUrl || typeof videoUrl !== 'string') continue;
1302                                const vpMatch = videoUrl.match(/(?:videopress\.com\/v\/)([a-zA-Z0-9]+)/);
1303                                if (!vpMatch) continue;
1304                                videoId = vpMatch[1];
1305                                hostname = 'videopress.com';
1306                                embedUrl = `https://videopress.com/embed/${videoId}`;
1307                                break;
1308                                
1309                            case 'hosted':
1310                                // Self-hosted videos - check if they should be blocked.
1311                                const externalUrl = parsed.external_url;
1312                                externalUrlEnabled = parsed.external_url_enabled === 'yes';
1313                                
1314                                if (externalUrlEnabled && externalUrl && typeof externalUrl === 'string') {
1315                                    // External URL is enabled and provided - treat as third-party content.
1316                                    try {
1317                                        const url = new URL(externalUrl);
1318                                        hostname = url.hostname;
1319                                        embedUrl = externalUrl;
1320                                    } catch (urlError) {
1321                                        // If external URL is invali
1321d, skip this video.
1322                                        continue;
1323                                    }
1324                                } else {
1325                                    // External URL is disabled or not provided - this is a WordPress media file.
1326                                    // For self-hosted videos, we should block them by default unless they have data-wcc="necessary".
1327                                    const videoSrc = node.src || node.getAttribute('src');
1328                                    if (videoSrc && typeof videoSrc === 'string') {
1329                                        try {
1330                                            const url = new URL(videoSrc);
1331											
1332
1333                                            // Block self-hosted videos unless they're marked as necessary.
1334                                            // Check if the video element has data-wcc attribute.
1335                                            if (node.hasAttribute('data-wcc') && node.getAttribute('data-wcc') === 'necessary') {
1336                                                // Video is marked as necessary - skip blocking.
1337                                                continue;
1338                                            }
1339                                            // Block the video and use the full path for more specific blocking.
1340                                            hostname = `${url.hostname}${url.pathname}`;
1341                                            embedUrl = videoSrc;
1342                                            
1343
1344                                        } catch (urlError) {
1345                                            // If video src is invalid, skip this video.
1346                                            continue;
1347                                        }
1348                                    } else {
1349                                        // No video src found - skip blocking.
1350                                        continue;
1351                                    }
1352                                }
1353                                break;
1354                                
1355                            default:
1356                                // Unknown video source, skip.
1357                                continue;
1358                        }
1359                        
1360                        // Skip if no valid video configuration found
1361                        if (!embedUrl || !hostname) continue;
1362
1363                        const uniqueID = ref._wccRandomString(8, false);
1364
1365                        // Real iframe for restoring after consent.
1366                        const realIframe = document.createElement('iframe');
1367                        realIframe.src = embedUrl;
1368                        realIframe.width = widgetContainer.offsetWidth || 560;
1369                        realIframe.height = widgetContainer.offsetHeight || 315;
1370                        realIframe.frameBorder = 0;
1371                        
1372                        // Set appropriate allow attributes based on video source.
1373                        if (hostname === 'youtube.com') {
1374                            realIframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture");
1375                        } else if (hostname === 'vimeo.com') {
1376                            realIframe.setAttribute("allow", "autoplay; fullscreen; picture-in-picture");
1377                        } else {
1378                            realIframe.setAttribute("allow", "autoplay; fullscreen");
1379                        }
1380
1381                        // Pseudo iframe for placeholder.
1382                        const pseudoIframe = {
1383                            nodeName: 'iframe',
1384                            src: realIframe.src,
1385                            offsetWidth: realIframe.width,
1386                            offsetHeight: realIframe.height,
1387                            insertAdjacentHTML: function (position, html) {
1388                                node.insertAdjacentHTML(position, html);
1389                            }
1390                        };
1391
1392                        const cleanedHostname = _wccCleanHostName(hostname);
1393                        _wccAddProviderToList(node, cleanedHostname);
1394                        
1395                        // Check if element has data-wcc attribute that should prevent blocking.
1396                        if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) {
1397                            continue;
1398                        }
1399                        // Check if provider should be blocked.
1400						if (!_wccShouldBlockProvider(cleanedHostname)) {
1401							// Provider is allowed, so make sure the real iframe exists.
1402							if (node.nodeName.toLowerCase() === 'div' && node.classList.contains('elementor-video')) {
1403								if (!node.querySelector('iframe')) {
1404									node.replaceWith(realIframe);
1405								}
1406							}
1407							continue;
1408						}
1409
1410                        const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname);
1411                        _wccAddPlaceholder(pseudoIframe, uniqueID, category);
1412
1413                        // For self-hosted videos, store the original video element, not the iframe.
1414                        const backupNode = (videoType === 'hosted' && !externalUrlEnabled) ? 
1415                            node.cloneNode(true) : // Store original video element for self-hosted.
1416                            realIframe.cloneNode(true); // Store iframe for external videos.
1417                        
1418                        node.remove();
1419                        _wccStore._backupNodes.push({
1420                            position: 'body',
1421                            node: backupNode,
1422                            uniqueID
1423                        });
1424                    } catch (e) {
1425                        console.error('Error processing Elementor video:', e);
1426                    }
1427
1428                    continue;
1429                }
1430
1431                // Original script/iframe blocking.
1432                if (!node.src || !node.nodeName || !["script", "iframe"].includes(node.nodeName.toLowerCase()))
1433                    continue;
1434
1435                try {
1436                    let urlToParse = node.src;
1437                    let hostname, pathname;
1438
1439
1440                    if (urlToParse.match(/^\/[a-zA-Z0-9]+\/?$/)) {
1441                        hostname = window.location.hostname;
1442                        pathname = urlToParse;
1443                    } else {
1444                        try {
1445                            const url = new URL(
1446                                urlToParse.startsWith("//") 
1447                                    ? `${window.location.protocol}${urlToParse}`
1448                                    : urlToParse.startsWith("/")
1449                                        ? `${window.location.origin}${urlToParse}`
1450                                        : urlToParse.match(/^https?:\/\//)
1451                                            ? urlToParse
1452                                            : `${window.location.origin}/${urlToParse}`
1453                            );
1454                            hostname = url.hostname;
1455                            pathname = url.pathname;
1456                        } catch (urlError) {
1457                            hostname = window.location.hostname;
1458                            pathname = urlToParse.startsWith('/') ? urlToParse : `/${urlToParse}`;
1459                        }
1460                    }
1461
1462                    const cleanedHostname = _wccCleanHostName(`${hostname}${pathname}`);
1463                    _wccAddProviderToList(node, cleanedHostname);
1464                    
1465                    // Check if element has data-wcc attribute that should prevent blocking.
1466                    if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) {
1467                        continue;
1468                    }
1469                    
1470                    // Check if provider should be blocked.
1471                    if (!_wccShouldBlockProvider(cleanedHostname)) continue;
1472
1473                    const uniqueID = ref._wccRandomString(8, false);
1474                    if (node.nodeName.toLowerCase() === "iframe") {
1475                        const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname);
1476                        _wccAddPlaceholder(node, uniqueID, category);
1477                    } else {
1478                        node.type = "javascript/blocked";
1479                        const scriptEventListener = function (event) {
1480                            event.preventDefault();
1481                            node.removeEventListener("beforescriptexecute", scriptEventListener);
1482                        };
1483                        node.addEventListener("beforescriptexecute", scriptEventListener);
1484                    }
1485
1486                    const position = document.head.compareDocumentPosition(node) & 
1487                        Node.DOCUMENT_POSITION_CONTAINED_BY ? "head" : "body";
1488
1489                    node.remove();
1490                    _wccStore._backupNodes.push({
1491                        position: position,
1492                        node: node.cloneNode(),
1493                        uniqueID
1494                    });
1495                } catch (error) {
1496                    console.log('Script processing error:', {
1497                        src: node.src,
1498                        nodeType: node.nodeName,
1499                        error: error.message
1500                    });
1501                    continue;
1502                }
1503            } catch (error) {
1504                console.error('Error in mutation observer:', error);
1505            }
1506            if (node.nodeType === 1) {
1507                _wccApplyCustomPlaceholders(node);
1508            }
1509        }
1510    }
1511}
1512
1513/**
1514 * Re-apply iframe blocking for embeds already in the DOM after consent is withdrawn.
1515 * MutationObserver only sees newly added nodes; restored YouTube/iframes stay playable until this runs.
1516 */
1517function _wccReblockExistingEmbeds() {
1518    const urlParams = new URLSearchParams(window.location.search);
1519    if (urlParams.has('wcc_bypass')) return;
1520
1521    const iframes = document.querySelectorAll('iframe[src]');
1522    for (const node of iframes) {
1523        try {
1524            if (!node.src) continue;
1525            if (node.closest('.wcc-consent-container')) continue;
1526            if (
1527                node.closest(
1528                    '.video-placeholder-normal, .video-placeholder-youtube, .video-placeholder-tiktok, [data-tag="video-placeholder"]'
1529                )
1530            ) {
1531                continue;
1532            }
1533
1534            let urlToParse = node.src;
1535            let hostname;
1536            let pathname;
1537
1538            if (urlToParse.match(/^\/[a-zA-Z0-9]+\/?$/)) {
1539                hostname = window.location.hostname;
1540                pathname = urlToParse;
1541            } else {
1542                try {
1543                    const url = new URL(
1544                        urlToParse.startsWith("//")
1545                            ? `${window.location.protocol}${urlToParse}`
1546                            : urlToParse.startsWith("/")
1547                                ? `${window.location.origin}${urlToParse}`
1548                                : urlToParse.match(/^https?:\/\//)
1549                                    ? urlToParse
1550                                    : `${window.location.origin}/${urlToParse}`
1551                    );
1552                    hostname = url.hostname;
1553                    pathname = url.pathname;
1554                } catch (urlError) {
1555                    hostname = window.location.hostname;
1556                    pathname = urlToParse.startsWith("/") ? urlToParse : `/${urlToParse}`;
1557                }
1558            }
1559
1560            const cleanedHostname = _wccCleanHostName(`${hostname}${pathname}`);
1561
1562            if (
1563                node.hasAttribute("data-wcc") &&
1564                !_wccIsCategoryToBeBlocked(node.getAttribute("data-wcc"))
1565            ) {
1566                continue;
1567            }
1568
1569            if (!_wccShouldBlockProvider(cleanedHostname)) continue;
1570
1571            const uniqueID = ref._wccRandomString(8, false);
1572            const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname);
1573            const w = node.offsetWidth || node.getBoundingClientRect().width || 560;
1574            const h = node.offsetHeight || node.getBoundingClientRect().height || 315;
1575            const pseudoIframe = {
1576                nodeName: "iframe",
1577                src: node.src,
1578                offsetWidth: w,
1579                offsetHeight: h,
1580                insertAdjacentHTML: function (position, html) {
1581                    node.insertAdjacentHTML(position, html);
1582                },
1583            };
1584
1585            _wccAddPlaceholder(pseudoIframe, uniqueID, category);
1586            const backupNode = node.cloneNode(true);
1587            node.remove();
1588            _wccStore._backupNodes.push({
1589                position: "body",
1590                node: backupNode,
1591                uniqueID,
1592            });
1593        } catch (e) {
1594            console.error("Error in _wccReblockExistingEmbeds:", e);
1595        }
1596    }
1597    _wccSetPlaceHolder();
1598    _wccApplyCustomPlaceholders();
1599}
1600
1601function _wccUnblock() {
1602    if (navigator.doNotTrack === 1) return;
1603
1604    const consent = ref._wccGetFromStore("consent");
1605    if (
1606        (_wccGetLaw() === "gdpr" &&
1607        (!consent || consent !== "yes") && showBanner)
1608    )
1609        return;
1610
1611    _wccStore._backupNodes = _wccStore._backupNodes.filter(
1612        (entry) => {
1613            const { position, node, uniqueID } = entry;
1614            try {
1615                if (entry.wccPh) {
1616                    const phCategories = entry.categories || [];
1617                    if (_wccGetBlockedPlaceholderCategories(phCategories).length) return true;
1618                    document.getElementById(uniqueID)?.remove();
1619                    if (node) {
1620                        node.style.display = '';
1621                        delete node.dataset.wccPh;
1622                    }
1623                    return false;
1624                }
1625                if (_wccShouldBlockProvider(node.src)) return true;
1626
1627                const frame = document.getElementById(uniqueID);
1628
1629                // Handle TikTok embeds
1630                const isTikTok = node.nodeName === 'BLOCKQUOTE' &&
1631                                 node.classList.contains('tiktok-embed');
1632
1633                if (isTikTok && frame) {
1634                    frame.parentNode.insertBefore(node, frame);
1635                    const script = document.createElement('script');
1636                    script.src = 'https://www.tiktok.com/embed.js';
1637                    script.async = true;
1638                    document.body.appendChild(script);
1639                    frame.parentNode.removeChild(frame);
1640                    return false;
1641                }
1642
1643                // Handle script-like nodes (including type="javascript/blocked")
1644                const isScriptLike = (
1645                    node.src &&
1646                    node.type &&
1647                    node.type.startsWith("javascript")
1648                );
1649
1650                if (isScriptLike) {
1651                    const scriptNode = document.createElement("script");
1652                    scriptNode.src = node.src;
1653                    scriptNode.type = "text/javascript";
1654                    scriptNode.async = true;
1655
1656                    const target = (position === "head") ? document.head : document.body;
1657                    target.appendChild(scriptNode);
1658
1659                    if (frame) frame.parentNode.removeChild(frame);
1660                    return false;
1661                }
1662
1663                // Handle iframe-based embeds if frame exists
1664                if (frame) {
1665                    // Check if this is a restored Elementor video iframe (has all the attributes we set)
1666                    if (node.frameBorder !== undefined && node.hasAttribute && node.hasAttribute('allow')) {
1667                        // This is an Elementor video iframe - restore it with all attributes
1668                        frame.parentNode.insertBefore(node, frame);
1669                        frame.parentNode.removeChild(frame);
1670                    } else if (node.nodeName === 'VIDEO') {
1671                        // This is a self-hosted video element - restore it directly
1672                        frame.parentNode.insertBefore(node, frame);
1673                        frame.parentNode.removeChild(frame);
1674                    } else {
1675                        // Generic iframe restoration
1676                        const iframe = document.createElement("iframe");
1677                        iframe.src = node.src;
1678                        iframe.width = frame.offsetWidth;
1679                        iframe.height = frame.offsetHeight;
1680                        frame.parentNode.insertBefore(iframe, frame);
1681                        frame.parentNode.removeChild(frame);
1682                    }
1683                }
1684
1685                return false;
1686            } catch (error) {
1687                console.error(error);
1688                return false;
1689            }
1690        }
1691    );
1692}
1693
1694function _wccClearListeners() {
1695    _nodeListObserver.disconnect();
1696    document.createElement = _wccCreateElementBackup;
1697}
1698function _wccAddProviderToList(node, cleanedHostname) {
1699    const categoryName =
1700        node.hasAttribute("data-wcc") && node.getAttribute("data-wcc");
1701    if (!categoryName) return;
1702    for (const category of _wccStore._categories)
1703        if (category.isNecessary && category.slug === categoryName && true === category.defaultConsent[currentLaw]) return;
1704    const provider = _wccStore._providersToBlock.find(
1705        ({ re }) => re === cleanedHostname
1706    );
1707    if (!provider)
1708        _wccStore._providersToBlock.push({
1709            re: cleanedHostname,
1710            categories: [categoryName],
1711            fullPath: false,
1712        });
1713    else if (!provider.isOverriden) {
1714        provider.categories = [categoryName];
1715        provider.isOverriden = true;
1716    } else if (!provider.categories.includes(categoryName))
1717        provider.categories.push(categoryName);
1718}
1719
1720const _nodeListObserver = new MutationObserver(_wccMutationObserver);
1721_nodeListObserver.observe(document.documentElement, {
1722    childList: true,
1723    subtree: true,
1724});
1725function _wccCleanHostName(name) {
1726    return name.replace(/^www./, "");
1727}
1728
1729function _wccIsCategoryToBeBlocked(category) {
1730    const cookieValue = ref._wccGetFromStore(category);
1731    const normalizedLaw = currentLaw.toLowerCase(); // Ensure case-insensitive key access
1732    return (
1733        cookieValue === "no" || 
1734        (!cookieValue &&
1735            _wccStore._categories.some(
1736                (cat) => cat.slug === category && !cat.isNecessary && !cat.defaultConsent[normalizedLaw]
1737            ))
1738    );
1739}
1740
1741function _wccShouldBlockProvider(formattedRE) {
1742    const provider = _wccStore._providersToBlock.find(({ re }) =>
1743        new RegExp(_wccEscapeRegex(re)).test(formattedRE)
1744    );
1745    return (
1746        provider &&
1747        provider.categories.some((category) => _wccIsCategoryToBeBlocked(category))
1748    );
1749}
1750
1751function _wccGetCategoryFromBlockedScript(element, cleanedHostname) {
1752    // Priority 1: Check data-wcc attribute
1753    if (element.hasAttribute && element.hasAttribute('data-wcc')) {
1754        const dataWcc = element.getAttribute('data-wcc');
1755        if (dataWcc && dataWcc !== 'necessary') {
1756            return [dataWcc]; // Return as array for consistency
1757        }
1758    }
1759    
1760    // Priority 2: Check provider pattern in _wccStore._providersToBlock
1761    if (cleanedHostname && _wccStore._providersToBlock) {
1762        const provider = _wccStore._providersToBlock.find(({ re }) =>
1763            new RegExp(_wccEscapeRegex(re)).test(cleanedHostname)
1764        );
1765        if (provider && provider.categories && provider.categories.length > 0) {
1766            // Return all categories that should be blocked, or all categories if none are blocked
1767            const blockedCategories = provider.categories.filter((category) => _wccIsCategoryToBeBlocked(category));
1768            return blockedCategories.length > 0 ? blockedCategories : provider.categories;
1769        }
1770    }
1771    
1772    // return null if no category is found
1773    return null;
1774}
1775function _wccShouldChangeType(element, src) {
1776    return (
1777        (element.hasAttribute("data-wcc") &&
1778            _wccIsCategoryToBeBlocked(
1779                element.getAttribute("data-wcc")
1780            )) ||
1781        _wccShouldBlockProvider(src ? src : element.src)
1782    );
1783}
1784
1785/**
1786 * Add readmore button to consent notice.
1787 * 
1788 * @returns void
1789 */
1790function _wccAttachReadMore() {
1791    const readMoreButton = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
1792        (code) => code.key === "wcc_readmore"
1793    );
1794    if (!readMoreButton.status) return;
1795    const content = readMoreButton.content;
1796    const styles = _wccStore._bannerConfig[currentLaw].config.readMore.styles;
1797    const readMoreElement = document.querySelector(
1798        '[data-tag="description"]'
1799    );
1800    if (!readMoreElement) return;
1801    if (readMoreElement.childNodes.length > 1) {
1802        const innerElement = document.querySelector(
1803            '[data-tag="description"] p:last-child'
1804        );
1805        innerElement && innerElement.insertAdjacentHTML(
1806            "beforeend",
1807            `&nbsp;${content}`
1808        );
1809    } else {
1810        readMoreElement.insertAdjacentHTML(
1811            "beforeend",
1812            `&nbsp;${content}`
1813        );
1814    }
1815    const placeHolders = document.querySelectorAll(
1816        `[data-tag="readmore-button"]`
1817    );
1818    if (placeHolders.length < 1) return;
1819    Array.from(placeHolders).forEach((placeHolder) => {
1820        for (const style in styles) {
1821            if (!styles[style]) continue;
1822            placeHolder.style[style] = styles[style];
1823        }
1824    });
1825}
1826
1827function _wccAfterConsent() {
1828    if (_wccGetLaw() === 'gdpr') _wccSetPreferenceCheckBoxStates(true);
1829    
1830    // Update consent details after consent action
1831    _wccSetConsentDetails();
1832    _wccSetFooterWrapperStyles();
1833
1834    if (_wccStore._bannerConfig[currentLaw].behaviours.reloadBannerOnAccept === true) {
1835        window.location.reload();
1836    }
1837}
1838
1839function _wccSendConsentEvent(responseCategories, status = "initial") {
1840    const consentUpdate = new CustomEvent('wcc_consent_update', {
1841        detail: {
1842            status: status,
1843            categories: responseCategories,
1844            currentLaw: currentLaw
1845        },
1846    });
1847    document.dispatchEvent(consentUpdate);
1848
1849    // Push consent data to dataLayer if it exists
1850    if (window.dataLayer) {
1851        window.dataLayer.push({
1852            "event": "wcc_cookie_consent_update",
1853            "cookie_consent_status": status,
1854            "cookie_consent_categories": responseCategories
1855        });
1856    }
1857}
1858
1859function _wccAttachNoticeStyles() {
1860    if (document.getElementById("wcc-style") || !_wccStyle) return;
1861    document.head.insertAdjacentHTML(
1862        "beforeend",
1863        ` <style id="wcc-style">${_wccStyle.css[currentLaw]}</style>`
1864    );
1865}
1866
1867function _wccFindCheckBoxValue(id = "") {
1868    const elemetsToCheck = id
1869        ? [`wccSwitch`, `wccCategoryDirect`]
1870        : [`wccCCPAOptOut`];
1871    return elemetsToCheck.some((key) => {
1872        const checkBox = document.getElementById(`${key}${id}`);
1873        return checkBox && checkBox.checked;
1874    });
1875}
1876
1877function _wccGetBlockedPlaceholderCategories(categories) {
1878    return categories.filter((slug) => _wccIsCategoryToBeBlocked(slug));
1879}
1880
1881function _wccFormatPlaceholderCategoryNames(categories) {
1882    return categories
1883        .map((catSlug) => {
1884            const foundCategory = _wccStore._categories?.find((cat) => cat.slug === catSlug);
1885            return foundCategory?.name || catSlug || '';
1886        })
1887        .filter(Boolean)
1888        .join(', ')
1889        .toLowerCase();
1890}
1891
1892function _wccUpdatePlaceholderCategoryText(uniqueID, categories) {
1893    const container = document.getElementById(uniqueID);
1894    if (!container) return;
1895    const innerTextElement = container.querySelector('[data-tag="placeholder-title"]')
1896        || container.querySelector(
1897            '.video-placeholder-text-normal, .video-placeholder-text-youtube, .video-placeholder-text-tiktok'
1898        );
1899    if (!innerTextElement) return;
1900    const template = innerTextElement.dataset.wccPlaceholderTemplate
1901        || innerTextElement.textContent
1902        || innerTextElement.innerText;
1903    if (!template.includes('{category}')) return;
1904    innerTextElement.dataset.wccPlaceholderTemplate = template;
1905    innerTextElement.textContent = template.replace(
1906        '{category}',
1907        _wccFormatPlaceholderCategoryNames(categories)
1908    );
1909    innerTextElement.style.display = 'block';
1910}
1911
1912/** Selector targets: { selector, categories } — same shape as _providersToBlock ({ re, categories }). */
1913function _wccApplyCustomPlaceholders(root = document) {
1914    const targets = _wccStore._customPlaceholderTargets;
1915    if (!targets?.length || !_wccStore._bannerConfig[currentLaw]?.config?.videoPlaceholder?.status) return;
1916    const scope = root?.nodeType === 1 ? root : document;
1917    targets.forEach(({ selector, categories = [] }) => {
1918        const blockedCategories = _wccGetBlockedPlaceholderCategories(categories);
1919        if (!selector || !blockedCategories.length) return;
1920        let nodes;
1921        try {
1922            nodes = scope === document
1923                ? document.querySelectorAll(selector)
1924                : [...(scope.matches(selector) ? [scope] : []), ...scope.querySelectorAll(selector)];
1925        } catch (e) {
1926            return;
1927        }
1928        nodes.forEach((node) => {
1929            if (!node.offsetWidth && !node.offsetHeight) return;
1930            if (node.closest('.wcc-consent-container,[data-tag="video-placeholder"]')) return;
1931
1932            if (node.dataset.wccPh) {
1933                node.setAttribute('data-wcc', blockedCategories.join(','));
1934                _wccUpdatePlaceholderCategoryText(node.dataset.wccPh, blockedCategories);
1935                const backup = _wccStore._backupNodes.find((entry) => entry.wccPh && entry.node === node);
1936                if (backup) backup.categories = blockedCategories;
1937                return;
1938            }
1939
1940            const uniqueID = ref._wccRandomString(8, false);
1941            const pseudo = {
1942                nodeName: 'iframe',
1943                src: '',
1944                offsetWidth: node.offsetWidth || 300,
1945                offsetHeight: node.offsetHeight || 200,
1946                insertAdjacentHTML(position, html) {
1947                    node.insertAdjacentHTML(position, html);
1948                },
1949            };
1950            node.dataset.wccPh = uniqueID;
1951            node.setAttribute('data-wcc', blockedCategories.join(','));
1952            _wccAddPlaceholder(pseudo, uniqueID, blockedCategories);
1953            node.style.display = 'none';
1954            _wccStore._backupNodes.push({
1955                position: 'body',
1956                node,
1957                uniqueID,
1958                wccPh: true,
1959                categories: blockedCategories,
1960            });
1961        });
1962    });
1963    _wccSetPlaceHolder();
1964}
1965
1966ref._wccIsConsentGrantedForCategory = (category) =>
1967    category ? !_wccIsCategoryToBeBlocked(category) : false;
1968
1969function _wccAddPlaceholder(htmlElm, uniqueID, categories = null) {
1970    const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
1971        (code) => code.key === 'wcc_video_placeholder'
1972    );
1973    if (!shortCodeData?.content) return;
1974    const videoPlaceHolderDataCode = shortCodeData.content;
1975    const { offsetWidth, offsetHeight } = htmlElm;
1976    if (offsetWidth === 0 || offsetHeight === 0) return;
1977    
1978    htmlElm.insertAdjacentHTML
1979        ? htmlElm.insertAdjacentHTML("beforebegin", `${videoPlaceHolderDataCode}`.replace("[UNIQUEID]", uniqueID))
1980        : document.body.insertAdjacentHTML("beforeend", `${videoPlaceHolderDataCode}`.replace("[UNIQUEID]", uniqueID));
1981
1982    const addedNode = document.getElementById(uniqueID);
1983    if (!addedNode) return;
1984    addedNode.style.width = `${offsetWidth}px`;
1985    addedNode.style.height = `${offsetHeight}px`;
1986    
1987    const innerTextElement = document.querySelector(
1988        `#${uniqueID} .video-placeholder-text-normal`
1989    );
1990
1991    // Check for TikTok URL
1992    const isTikTok = htmlElm.src && htmlElm.src.includes('tiktok.com');
1993    const youtubeID = !isTikTok && htmlElm.src && _wccGetYoutubeID(htmlElm.src);
1994
1995    if (innerTextElement && (youtubeID || isTikTok)) {
1996        innerTextElement.style.display = "none";
1997    }
1998    
1999    if (youtubeID && addedNode) {
2000        addedNode.classList.replace(
2001            "video-placeholder-normal",
2002            "video-placeholder-youtube"
2003        );
2004        addedNode.style.backgroundImage = `linear-gradient(rgba(76,72,72,.7),rgba(76,72,72,.7)),url('https://img.youtube.com/vi/${youtubeID}/maxresdefault.jpg')`;
2005        innerTextElement?.classList.replace(
2006            "video-placeholder-text-normal",
2007            "video-placeholder-text-youtube"
2008        );
2009    } else if (isTikTok && addedNode) {
2010        addedNode.classList.replace(
2011            "video-placeholder-text-normal",
2012            "video-placeholder-tiktok"
2013        );
2014        innerTextElement?.classList.replace(
2015            "video-placeholder-text-tiktok",
2016            "video-placeholder-text-normal"
2017        );
2018    }
2019    if (innerTextElement) {
2020        const categoryList = Array.isArray(categories) ? categories : categories ? [categories] : [];
2021        _wccUpdatePlaceholderCategoryText(uniqueID, categoryList);
2022    }
2023
2024    if (innerTextElement && !youtubeID && !isTikTok) {
2025        innerTextElement.style.display = "block";
2026    }
2027}
2028function _wccGetYoutubeID(src) {
2029    const match = src.match(
2030        /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
2031    );
2032    if (match && Array.isArray(match) && match[2] && match[2].length === 11)
2033        return match[2];
2034    return false;
2035}
2036
2037function _wccSetPlaceHolder() {
2038    const status = _wccStore._bannerConfig[currentLaw].config.videoPlaceholder.status;
2039    const styles = _wccStore._bannerConfig[currentLaw].config.videoPlaceholder.styles;
2040    if (!status) return;
2041    const placeHolders = document.querySelectorAll(
2042        `[data-tag="placeholder-title"]`
2043    );
2044    if (placeHolders.length < 1) return;
2045    Array.from(placeHolders).forEach((placeHolder) => {
2046        placeHolder.style.display = "block";
2047        if (!placeHolder.dataset.wccPlaceholderBound) {
2048            placeHolder.dataset.wccPlaceholderBound = "1";
2049            placeHolder.addEventListener("click", () => {
2050                if (ref._wccGetFromStore("action")) _revisitWccConsent();
2051            });
2052        }
2053        for (const style in styles) {
2054            if (!styles[style]) continue;
2055            placeHolder.style[style] = styles[style];
2056        }
2057    });
2058}
2059function _wccAddRtlClass() {
2060    if (!_wccStore._rtl) return;
2061    const rtlElements = ['notice', 'detail', 'optout-popup', 'revisit-consent', 'video-placeholder']
2062    rtlElements.forEach(function (item) {
2063        _wccGetElementByTag(item) && _wccGetElementByTag(item).classList.add('wcc-rtl');
2064    });
2065}
2066
2067function _wccSetFocus(tagName) {
2068    const element = _wccGetElementByTag(tagName);
2069    if (!element) return;
2070    element.focus();
2071}
2072
2073function _wccSetPoweredBy() {
2074    // Cache configuration access to avoid repeated deep property lookups
2075    const poweredByStatus = _wccStore._bannerConfig[currentLaw].config.preferenceCenter.poweredBy.status;
2076    
2077    // Set flag for footer wrapper styles
2078    _wccStore._poweredByHidden = !poweredByStatus;
2079    
2080    // Define powered-by element selectors once
2081    const poweredBySelectors = ['detail-powered-by', 'optout-powered-by'];
2082    
2083    // Determine styles based on powered-by status
2084    const styles = poweredByStatus ? {
2085        display: "flex",
2086        justifyContent: "flex-end",
2087        alignItems: "center"
2088    } : {
2089        display: "none"
2090    };
2091    
2092    // Apply styles to all powered-by elements in a single loop
2093    poweredBySelectors.forEach((key) => {
2094        const element = document.querySelector(`[data-tag="${key}"]`);
2095        if (element) {
2096            Object.assign(element.style, styles);
2097        }
2098    });
2099}
2100function _wccWatchBannerElement() {
2101    document.querySelector("body").addEventListener("click", (event) => {
2102        const selector = ".wcc-banner-element, .wcc-banner-element *";
2103        if (
2104            event.target.matches
2105                ? event.target.matches(selector)
2106                : event.target.msMatchesSelector(selector)
2107        )
2108            _revisitWccConsent();
2109    });
2110}
2111
2112function _wccRemoveAllDeadCookies() {
2113    for (const category of _wccStore._categories) {
2114        if (ref._wccGetFromStore(category.slug) !== "yes")
2115            _wccRemoveDeadCookies(category);
2116    }
2117}
2118function _wccSetNoticeGpcOption() {
2119    const noticeGpcWrapper = document.querySelector('[data-tag="notice-gpc-option"]');
2120    if (!noticeGpcWrapper) {
2121        return;
2122    }
2123
2124    if (_wccGPCEnabled()) {
2125        noticeGpcWrapper.classList.remove('wcc-hide');
2126    } else {
2127        noticeGpcWrapper.classList.add('wcc-hide');
2128    }
2129}
2130function _wccSetCCPAOptions() {
2131    const toggle = _wccStore._bannerConfig[currentLaw].config.optOption.toggle;
2132    const activeColor = toggle.states.active.styles['background-color'];
2133    const inactiveColor = toggle.states.inactive.styles['background-color'];
2134    _wccClassRemove("=optout-option", "wcc-disabled", false);
2135    const toggleDataCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
2136        (code) => code.key === "wcc_optout_toggle_label"
2137    );
2138    const optOutTitle = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find(
2139        (code) => code.key === "wcc_optout_option_title"
2140    );
2141    const formattedLabel = toggleDataCode.content.replace(
2142        `[wcc_optout_option_title]`,
2143        optOutTitle.content
2144    );
2145    // Opt-out checked when user has revoked sale consent (store "no") or GPC applies.
2146    let checked = _wccGPCEnabled() || ref._wccGetFromStore("consent") === "no";  
2147    // Disable checkbox if GPC is enabled (user cannot change the setting)
2148    let isDisabled = _wccGPCEnabled();
2149    
2150    _wccSetCheckBoxInfo(
2151        document.getElementById(`wccCCPAOptOut`),
2152        formattedLabel,
2153        {
2154            checked,
2155            disabled: isDisabled,
2156            addListeners: !isDisabled, // Don't add listeners if GPC is enabled (prevents click on opt-out title)
2157        },
2158        { activeColor, inactiveColor },
2159        true
2160    );
2161    
2162    // Show/hide GPC wrapper based on GPC status
2163    const gpcWrapper = document.querySelector('[data-tag="optout-gpc-option"]');
2164    if (gpcWrapper) {
2165        if (_wccGPCEnabled()) {
2166            gpcWrapper.style.display = '';
2167        } else {
2168            gpcWrapper.style.display = 'none';
2169        }
2170    }
2171    
2172    // If GPC is enabled, ensure checkbox is checked and disabled, and prevent further interactions
2173    if (_wccGPCEnabled()) {
2174        const optOutCheckbox = document.getElementById(`wccCCPAOptOut`);
2175        if (optOutCheckbox) {
2176            // Ensure checkbox is checked and disabled
2177            optOutCheckbox.checked = true;
2178            optOutCheckbox.disabled = true;
2179            // Find the wrapper element to set cursor on hover
2180            const checkboxWrapper = optOutCheckbox.closest('[data-tag="optout-option"], .wcc-opt-out-checkbox-wrapper');
2181            if (checkboxWrapper) {
2182                // Set cursor on wrapper so it works on hover
2183                checkboxWrapper.style.cursor = 'no-drop';
2184            }
2185            // Prevent clicks on the checkbox container
2186            const checkboxContainer = optOutCheckbox.closest('.wcc-opt-out-checkbox, [data-tag="optout-gpc-option"]');
2187            if (checkboxContainer) {
2188                checkboxContainer.style.pointerEvents = 'none';
2189                checkboxContainer.style.opacity = '0.6';
2190            }
2191            // Disable the opt-out option title click as well
2192            const optOutTitle = document.querySelector('[data-tag="optout-option-title"]');
2193            if (optOutTitle) {
2194                optOutTitle.style.pointerEvents = 'none';
2195            }
2196            // Add cursor style to checkbox
2197            optOutCheckbox.style.cursor = 'no-drop';
2198        }
2199    }
2200}
2201function _wccSetCheckBoxInfo(
2202    boxElem,
2203    formattedLabel,
2204    { checked, disabled, addListeners },
2205    { activeColor, inactiveColor },
2206    isCCPA = false
2207) {
2208    if (!boxElem) return;
2209    if (isCCPA && addListeners)
2210        _wccAttachListener("=optout-option-title", () => boxElem.click());
2211    boxElem.checked = checked;
2212    boxElem.disabled = disabled;
2213    boxElem.style.backgroundColor = checked ? activeColor : inactiveColor;
2214    _wccSetCheckBoxAriaLabel(boxElem, checked, formattedLabel, isCCPA);
2215    if (!addListeners) return;
2216    boxElem.addEventListener("change", ({ currentTarget: elem }) => {
2217        const isChecked = elem.checked;
2218        elem.style.backgroundColor = isChecked ? activeColor : inactiveColor;
2219        _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel, isCCPA);
2220    });
2221}
2222
2223window.revisitWccConsent = () => _revisitWccConsent();
2224window.getWccConsent = function () {
2225    const cookieConsent = {
2226        currentLaw: "",
2227        categories: {},
2228        isUserActionCompleted: false,
2229        consentID: "",
2230        languageCode: ""
2231      };
2232      try {
2233        cookieConsent.currentLaw = _wccGetLaw();
2234  
2235        _wccStore._categories.forEach(category => {
2236            cookieConsent.categories[category.slug] = ref._wccGetFromStore(category.slug) === "yes";
2237         });
2238  
2239        cookieConsent.isUserActionCompleted = ref._wccGetFromStore("action") === "yes";
2240        cookieConsent.consentID = ref._wccGetFromStore("consentid") || "";
2241        cookieConsent.languageCode = _wccStore._language || "";
2242    } catch (e) {}
2243  
2244    return cookieConsent;
2245}; 
2246
2247/**
2248 * Record the consent of a visitor.
2249 * 
2250 * @returns {void}
2251 */
2252async function _wccLogConsent() {
2253    if (!_wccStore._logConsent) return;
2254    try {
2255        const logData = JSON.stringify(
2256            _wccStore._categories
2257                .map(({ slug }) => ({
2258                    name: slug,
2259                    status: ref._wccGetFromStore(slug) || "no",
2260                }))
2261                .concat([
2262                    {
2263                        name: "Cookie Consent",
2264                        status:
2265                            _wccStore._bannerConfig.activeLaw === "ccpa"
2266                                ? "yes"
2267                                : ref._wccGetFromStore("consent") || "no",
2268                    },
2269                ])
2270        );
2271        const data = {
2272            log: logData,
2273            consent_id: ref._wccGetFromStore("consentid"),
2274        };
2275        let request = new XMLHttpRequest();
2276        request.open('POST', `${_wccApi.base}consent_logs`, true);
2277        request.setRequestHeader('Content-type', 'application/json');
2278        request.setRequestHeader('X-WP-Nonce', _wccApi.nonce);
2279        request.send(JSON.stringify(data));
2280    } catch (err) {
2281        console.error(err);
2282    }
2283}
2284/**
2285 * Retrieve the last modified time of a cookie.
2286 * 
2287 * @param {string} cookieName - The name of the cookie
2288 * @returns {number | null} The last modified time of the cookie in milliseconds, or null if the cookie doesn't exist
2289 */
2290function _wccGetCookieLastModified(cookieName) {
2291    const cookieValue = document.cookie
2292        .split(';')
2293        .map(cookie => cookie.trim())
2294        .find(row => row.startsWith(`${cookieName}=`));
2295
2296    if (cookieValue) {
2297        const cookiePairs = cookieValue.substring(cookieName.length + 1).split(",");
2298        for (const pair of cookiePairs) {
2299            const [key, value] = pair.split(":");
2300            if (key === 'consent_time') {
2301                // Try to parse the value associated with the 'consent_time' key as a number
2302                const lastModifiedTime = Number(value);
2303                if (!isNaN(lastModifiedTime)) {
2304                    return lastModifiedTime;
2305                }
2306            }
2307        }
2308    }
2309    return null;
2310}
2311function _wccPrivacySignalDetected() {
2312    return navigator.globalPrivacyControl === true
2313        || navigator.doNotTrack === "1"
2314        || navigator.doNotTrack === 1;
2315}
2316
2317/**
2318 * Check if whether plugin Respect Do Not Track & Global Privacy Control
2319 * 
2320 * @since 3.2.0
2321 * @returns {boolean} True if the gpc option enabled in plugin settings and browser settings, false otherwise
2322 */
2323function _wccGPCEnabled() {
2324    return _wccPrivacySignalDetected() && _wccStore._bannerConfig[currentLaw].config.optOption.gpcOption;
2325}
2326
2327/**
2328 * Format a date according to PHP date format string
2329 * Converts PHP date format tokens to JavaScript date formatting
2330 * 
2331 * @param {Date} date The date object to format
2332 * @param {string} format PHP date format string (e.g., 'Y-m-d H:i:s')
2333 * @returns {string} Formatted date string
2334 */
2335function _wccFormatDate(date, format) {
2336    if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
2337        return '';
2338    }
2339    
2340    // PHP to JavaScript format mapping
2341    const formatMap = {
2342        'Y': () => date.getFullYear(), // 4-digit year
2343        'y': () => String(date.getFullYear()).slice(-2), // 2-digit year
2344        'm': () => String(date.getMonth() + 1).padStart(2, '0'), // 2-digit month (01-12)
2345        'n': () => String(date.getMonth() + 1), // Month without leading zeros (1-12)
2346        'd': () => String(date.getDate()).padStart(2, '0'), // 2-digit day (01-31)
2347        'j': () => String(date.getDate()), // Day without leading zeros (1-31)
2348        'H': () => String(date.getHours()).padStart(2, '0'), // 24-hour format (00-23)
2349        'h': () => {
2350            const hours = date.getHours() % 12 || 12;
2351            return String(hours).padStart(2, '0'); // 12-hour format (01-12)
2352        },
2353        'G': () => String(date.getHours()), // 24-hour format without leading zeros (0-23)
2354        'g': () => {
2355            const hours = date.getHours() % 12 || 12;
2356            return String(hours); // 12-hour format without leading zeros (1-12)
2357        },
2358        'i': () => String(date.getMinutes()).padStart(2, '0'), // Minutes with leading zeros (00-59)
2359        's': () => String(date.getSeconds()).padStart(2, '0'), // Seconds with leading zeros (00-59)
2360        'A': () => date.getHours() >= 12 ? 'PM' : 'AM', // Uppercase AM/PM
2361        'a': () => date.getHours() >= 12 ? 'pm' : 'am', // Lowercase am/pm
2362    };
2363    
2364    let formatted = '';
2365    let i = 0;
2366    
2367    while (i < format.length) {
2368        // Check for escape character
2369        if (format[i] === '\\' && i + 1 < format.length) {
2370            formatted += format[i + 1];
2371            i += 2;
2372            continue;
2373        }
2374        
2375        // Check if current character is a format token
2376        let matched = false;
2377        for (const [token, formatter] of Object.entries(formatMap)) {
2378            if (format.substr(i, token.length) === token) {
2379                formatted += formatter();
2380                i += token.length;
2381                matched = true;
2382                break;
2383            }
2384        }
2385        
2386        // If no token matched, add the character as-is
2387        if (!matched) {
2388            formatted += format[i];
2389            i++;
2390        }
2391    }
2392    
2393    return formatted;
2394}
2395
2396/**
2397 * Set consent details (ID and time) and handle accordion functionality
2398 * Hide the section if consent is not given
2399 * 
2400 * @returns {void}
2401 */
2402function _wccSetConsentDetails() {
2403    const consentDetailsWrappers = document.querySelectorAll('[data-tag="consent-details"]');
2404    if (!consentDetailsWrappers || consentDetailsWrappers.length === 0) {
2405        // Set flag if no consent details elements exist
2406        _wccStore._consentDetailsHidden = true;
2407        return;
2408    }
2409
2410    // Check if consent has been given
2411    const action = ref._wccGetFromStore("action");
2412    const consent = ref._wccGetFromStore("consent");
2413    const hasConsent = action === "yes";
2414
2415    // Set flag for footer wrapper styles
2416    _wccStore._consentDetailsHidden = !hasConsent;
2417
2418    consentDetailsWrappers.forEach((wrapper) => {
2419        // Hide the entire section if consent is not given
2420        if (!hasConsent) {
2421            wrapper.classList.add('wcc-hide');
2422            return;
2423        }
2424
2425        // Show the section
2426        wrapper.classList.remove('wcc-hide');
2427
2428        // Get consent ID and time from cookie value
2429        const cookieValue = ref._wccGetCookie("wt_consent");
2430        let consentID = '';
2431        let consentTime = '';
2432
2433        if (cookieValue) {
2434            try {
2435                // Parse cookie value: "key1:value1,key2:value2,..."
2436                const cookiePairs = cookieValue.split(",");
2437                for (const pair of cookiePairs) {
2438                    const [key, value] = pair.split(":");
2439                    if (key === 'consentid' && value) {
2440                        consentID = value;
2441                    } else if (key === 'consent_time' && value) {
2442                        consentTime = value;
2443                    }
2444                }
2445            } catch (e) {
2446                // If parsing fails, try to get from store as fallback
2447                consentID = ref._wccGetFromStore("consentid") || '';
2448                consentTime = ref._wccGetFromStore("consent_time") || '';
2449            }
2450        } else {
2451            // Fallback to store if cookie not found
2452            consentID = ref._wccGetFromStore("consentid") || '';
2453            consentTime = ref._wccGetFromStore("consent_time") || '';
2454        }
2455
2456        // Format consent time using the configured date format
2457        let formattedTime = '';
2458        if (consentTime) {
2459            try {
2460                const timeValue = parseInt(consentTime, 10);
2461                if (!isNaN(timeValue)) {
2462                    const date = new Date(timeValue);
2463                    // Get date format from config
2464                    const dateFormat = _wccStore._bannerConfig && _wccStore._bannerConfig[currentLaw] && _wccStore._bannerConfig[currentLaw].settings && _wccStore._bannerConfig[currentLaw].settings.consentDetails && _wccStore._bannerConfig[currentLaw].settings.consentDetails.dateFormat
2465                        ? _wccStore._bannerConfig[currentLaw].settings.consentDetails.dateFormat
2466                        : 'Y-m-d H:i:s'; // Default format
2467                    formattedTime = _wccFormatDate(date, dateFormat);
2468                }
2469            } catch (e) {
2470                formattedTime = consentTime;
2471            }
2472        }
2473
2474        // Populate consent details
2475        const consentIdElement = wrapper.querySelector('[data-tag="consent-id"]');
2476        const consentTimeElement = wrapper.querySelector('[data-tag="consent-time"]');
2477
2478        if (consentIdElement) {
2479            consentIdElement.textContent = consentID || 'N/A';
2480        }
2481        if (consentTimeElement) {
2482            consentTimeElement.textContent = formattedTime || 'N/A';
2483        }
2484
2485        // Handle accordion click - using standard accordion structure
2486        const accordion = wrapper.querySelector('#wccConsentDetails');
2487        if (accordion) {
2488            const accordionBody = accordion.querySelector('.wcc-accordion-body');
2489            const accordionHeaderWrapper = accordion.querySelector('.wcc-accordion-header-wrapper');
2490            
2491            if (accordionHeaderWrapper) {
2492                // Remove any existing click handlers by cloning the element
2493                const newHeaderWrapper = accordionHeaderWrapper.cloneNode(true);
2494                accordionHeaderWrapper.parentNode.replaceChild(newHeaderWrapper, accordionHeaderWrapper);
2495                
2496                // Get fresh references after replacement
2497                const freshAccordion = wrapper.querySelector('#wccConsentDetails');
2498                const freshAccordionBody = freshAccordion ? freshAccordion.querySelector('.wcc-accordion-body') : null;
2499                const freshAccordionHeaderWrapper = freshAccordion ? freshAccordion.querySelector('.wcc-accordion-header-wrapper') : null;
2500                
2501                if (freshAccordionHeaderWrapper && freshAccordion) {
2502                    freshAccordionHeaderWrapper.addEventListener('click', (e) => {
2503                        e.preventDefault();
2504                        e.stopPropagation();
2505                        
2506                        // Toggle accordion state
2507                        const isActive = freshAccordion.classList.toggle('wcc-accordion-active');
2508                        freshAccordion.removeAttribute('aria-expanded');
2509                        const consentBtn = freshAccordion.querySelector('.wcc-accordion-btn');
2510                        if (consentBtn) consentBtn.setAttribute('aria-expanded', isActive ? 'true' : 'false');
2511                        if (freshAccordionBody) {
2512                            freshAccordionBody.setAttribute('aria-hidden', isActive ? 'false' : 'true');
2513                        }
2514                    });
2515                }
2516            }
2517        }
2518    });
2519}
2520
2521/**
2522 * Apply custom CSS to footer wrapper when both consent details and PoweredBy are hidden
2523 * 
2524 * @returns {void}
2525 */
2526function _wccSetFooterWrapperStyles() {
2527    const poweredByHidden = _wccStore._poweredByHidden;
2528    const consentDetailsHidden = _wccStore._consentDetailsHidden;
2529    // Apply custom CSS when both are hidden
2530    if (poweredByHidden && consentDetailsHidden) {
2531        const footerWrapper = document.querySelector('.wcc-footer-bottom-wrapper');
2532        if (footerWrapper) {
2533            footerWrapper.style.display = "none";
2534        } 
2535    } else {
2536        const footerWrapper = document.querySelector('.wcc-footer-bottom-wrapper');
2537        if (footerWrapper) {
2538            footerWrapper.style.display = "flex";
2539        }
2540    }
2541}

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.