PageSourceSearch

https://assets.flexifunnels.com/js/cookie-consent-widget.js

js flexifunnels.com collected 2026-09-30 03:11:13 UTC 139,273 bytes, 2,900 lines download raw bytes

1/**
2 * FlexiFunnels Cookie Consent Widget
3 */
4(function () {
5    "use strict";
6
7    // Find our own <script> element
8    var scriptEl =
9        document.currentScript ||
10        (function () {
11            var scripts = document.getElementsByTagName("script");
12            for (var i = scripts.length - 1; i >= 0; i--) {
13                if (
14                    scripts[i].src &&
15                    scripts[i].src.indexOf("cookie-consent-widget") !== -1
16                )
17                    return scripts[i];
18            }
19            return null;
20        })();
21
22    var userConfig = window.cookieConsentConfig || {};
23
24    // Widget ID: data-widget-id attribute → fallback to window config
25    var CONFIG_ID =
26        (scriptEl && scriptEl.getAttribute("data-widget-id")) ||
27        userConfig.configId ||
28        "";
29
30    // API URL: derive from script src OR use explicit window config
31    var API_URL = "https://api-v1.flexifunnels.link/v1/stats/cookie-consent";
32    // if (userConfig.apiUrl) {
33    //     API_URL = userConfig.apiUrl.replace(/\/$/, "");
34    // } else if (scriptEl && scriptEl.src) {
35    //     var srcUrl = scriptEl.src.split("?")[0];
36    //     var jsIdx = srcUrl.lastIndexOf("/js/");
37    //     if (jsIdx !== -1) {
38    //         API_URL = srcUrl.substring(0, jsIdx) + "/api/cookie-consent";
39    //     }
40    // }
41
42    if (!CONFIG_ID || !API_URL) {
43        console.warn(
44            "[FF-Consent] Could not detect widget config. Use data-widget-id on the script tag or set window.cookieConsentConfig = { configId, apiUrl }",
45        );
46        return;
47    }
48
49    var CONSENT_COOKIE = "ff_consent";
50    var VISITOR_COOKIE = "ff_visitor_id";
51
52    var VALID_LAYOUTS = [
53        "bar",
54        "box",
55        "banner",
56        "popup",
57        "floating",
58        "sidebar",
59        "modal",
60        "cloud",
61    ];
62
63    var VALID_POSITIONS = [
64        "bottom",
65        "top",
66        "left",
67        "right",
68        "center",
69        "top-left",
70        "top-center",
71        "top-right",
72        "middle-left",
73        "middle-center",
74        "middle-right",
75        "bottom-left",
76        "bottom-center",
77        "bottom-right",
78    ];
79
80    // ──────────────────────────────────────────────
81    // Built-in Theme Presets
82    // ──────────────────────────────────────────────
83
84    var THEME_PRESETS = {
85        default: {
86            primary_color: "#2563EB",
87            secondary_color: "#1E40AF",
88            background_color: "#ffffff",
89            text_color: "#1F2937",
90            title_color: "#1F2937",
91            separator_color: "#E5E7EB",
92            footer_border_color: "#E5E7EB",
93            accept_btn: {
94                color: "#fff",
95                bg: "#2563EB",
96                border: "#2563EB",
97                hover_color: "#fff",
98                hover_bg: "#1D4ED8",
99                hover_border: "#1D4ED8",
100            },
101            reject_btn: {
102                color: "#fff",
103                bg: "#6B7280",
104                border: "#6B7280",
105                hover_color: "#fff",
106                hover_bg: "#4B5563",
107                hover_border: "#4B5563",
108            },
109            manage_btn: {
110                color: "#2563EB",
111                bg: "transparent",
112                border: "#2563EB",
113                hover_color: "#1D4ED8",
114                hover_bg: "#EFF6FF",
115                hover_border: "#1D4ED8",
116            },
117        },
118        theme1: {
119            primary_color: "#DC2626",
120            secondary_color: "#B91C1C",
121            background_color: "#ffffff",
122            text_color: "#1F2937",
123            title_color: "#1F2937",
124            separator_color: "#FEE2E2",
125            footer_border_color: "#FEE2E2",
126            accept_btn: {
127                color: "#fff",
128                bg: "#DC2626",
129                border: "#DC2626",
130                hover_color: "#fff",
131                hover_bg: "#B91C1C",
132                hover_border: "#B91C1C",
133            },
134            reject_btn: {
135                color: "#fff",
136                bg: "#6B7280",
137                border: "#6B7280",
138                hover_color: "#fff",
139                hover_bg: "#4B5563",
140                hover_border: "#4B5563",
141            },
142            manage_btn: {
143                color: "#DC2626",
144                bg: "transparent",
145                border: "#DC2626",
146                hover_color: "#B91C1C",
147                hover_bg: "#FEF2F2",
148                hover_border: "#B91C1C",
149            },
150        },
151        theme2: {
152            primary_color: "#16A34A",
153            secondary_color: "#15803D",
154            background_color: "#ffffff",
155            text_color: "#1F2937",
156            title_color: "#1F2937",
157            separator_color: "#DCFCE7",
158            footer_border_color: "#DCFCE7",
159            accept_btn: {
160                color: "#fff",
161                bg: "#16A34A",
162                border: "#16A34A",
163                hover_color: "#fff",
164                hover_bg: "#15803D",
165                hover_border: "#15803D",
166            },
167            reject_btn: {
168                color: "#fff",
169                bg: "#6B7280",
170                border: "#6B7280",
171                hover_color: "#fff",
172                hover_bg: "#4B5563",
173                hover_border: "#4B5563",
174            },
175            manage_btn: {
176                color: "#16A34A",
177                bg: "transparent",
178                border: "#16A34A",
179                hover_color: "#15803D",
180                hover_bg: "#F0FDF4",
181                hover_border: "#15803D",
182            },
183        },
184        theme3: {
185            primary_color: "#3B82F6",
186            secondary_color: "#2563EB",
187            background_color: "#ffffff",
188            text_color: "#1F2937",
189            title_color: "#1F2937",
190            separator_color: "#DBEAFE",
191            footer_border_color: "#DBEAFE",
192            accept_btn: {
193                color: "#fff",
194                bg: "#3B82F6",
195                border: "#3B82F6",
196                hover_color: "#fff",
197                hover_bg: "#2563EB",
198                hover_border: "#2563EB",
199            },
200            reject_btn: {
201                color: "#fff",
202                bg: "#6B7280",
203                border: "#6B7280",
204                hover_color: "#fff",
205                hover_bg: "#4B5563",
206                hover_border: "#4B5563",
207            },
208            manage_btn: {
209                color: "#3B82F6",
210                bg: "transparent",
211                border: "#3B82F6",
212                hover_color: "#2563EB",
213                hover_bg: "#EFF6FF",
214                hover_border: "#2563EB",
215            },
216        },
217        theme4: {
218            primary_color: "#111827",
219            secondary_color: "#374151",
220            background_color: "#ffffff",
221            text_color: "#1F2937",
222            title_color: "#111827",
223            separator_color: "#E5E7EB",
224            footer_border_color: "#E5E7EB",
225            accept_btn: {
226                color: "#fff",
227                bg: "#111827",
228                border: "#111827",
229                hover_color: "#fff",
230                hover_bg: "#374151",
231                hover_border: "#374151",
232            },
233            reject_btn: {
234                color: "#fff",
235                bg: "#6B7280",
236                border: "#6B7280",
237                hover_color: "#fff",
238                hover_bg: "#4B5563",
239                hover_border: "#4B5563",
240            },
241            manage_btn: {
242                color: "#111827",
243                bg: "transparent",
244                border: "#111827",
245                hover_color: "#374151",
246                hover_bg: "#F9FAFB",
247                hover_border: "#374151",
248            },
249        },
250    };
251
252    // ──────────────────────────────────────────────
253    // Multi-Language Support (25+ built-in languages)
254    // Server translations override these when available.
255    // ──────────────────────────────────────────────
256
257    var TRANSLATIONS = {
258        en: {
259            title: "We use cookies",
260            message:
261                "We use cookies to improve your experience on our website. By browsing this website, you agree to our use of cookies.",
262            accept: "Accept All",
263            reject: "Reject All",
264            manage: "Manage Preferences",
265            save: "Save Preferences",
266            withdraw: "Withdraw Consent",
267            cancel: "Cancel",
268            always_active: "Always Active",
269            cookies_used: "Cookies used:",
270            privacy: "Privacy Policy",
271            terms: "Terms & Conditions",
272            pref_title: "Manage Cookie Preferences",
273            pref_desc:
274                "Choose which cookies you want to accept. You can change your preferences at any time.",
275            powered_by: "Powered by",
276        },
277        es: {
278            title: "Usamos cookies",
279            message:
280                "Utilizamos cookies para mejorar su experiencia en nuestro sitio web. Al navegar por este sitio, acepta nuestro uso de cookies.",
281            accept: "Aceptar todo",
282            reject: "Rechazar todo",
283            manage: "Gestionar preferencias",
284            save: "Guardar preferencias",
285            withdraw: "Retirar consentimiento",
286            cancel: "Cancelar",
287            always_active: "Siempre activo",
288            cookies_used: "Cookies utilizadas:",
289            privacy: "Política de privacidad",
290            terms: "Términos y condiciones",
291            pref_title: "Gestionar preferencias de cookies",
292            pref_desc:
293                "Elija qué cookies desea aceptar. Puede cambiar sus preferencias en cualquier momento.",
294            powered_by: "Desarrollado por",
295        },
296        fr: {
297            title: "Nous utilisons des cookies",
298            message:
299                "Nous utilisons des cookies pour améliorer votre expérience sur notre site web. En naviguant sur ce site, vous acceptez notre utilisation des cookies.",
300            accept: "Tout accepter",
301            reject: "Tout refuser",
302            manage: "Gérer les préférences",
303            save: "Enregistrer",
304            withdraw: "Retirer le consentement",
305            cancel: "Annuler",
306            always_active: "Toujours actif",
307            cookies_used: "Cookies utilisés :",
308            privacy: "Politique de confidentialité",
309            terms: "Conditions générales",
310            pref_title: "Gérer les préférences de cookies",
311            pref_desc:
312                "Choisissez les cookies que vous souhaitez accepter. Vous pouvez modifier vos préférences à tout moment.",
313            powered_by: "Propulsé par",
314        },
315        de: {
316            title: "Wir verwenden Cookies",
317            message:
318                "Wir verwenden Cookies, um Ihre Erfahrung auf unserer Website zu verbessern. Durch das Surfen auf dieser Website stimmen Sie unserer Verwendung von Cookies zu.",
319            accept: "Alle akzeptieren",
320            reject: "Alle ablehnen",
321            manage: "Einstellungen verwalten",
322            save: "Einstellungen speichern",
323            withdraw: "Einwilligung widerrufen",
324            cancel: "Abbrechen",
325            always_active: "Immer aktiv",
326            cookies_used: "Verwendete Cookies:",
327            privacy: "Datenschutzrichtlinie",
328            terms: "Allgemeine Geschäftsbedingungen",
329            pref_title: "Cookie-Einstellungen verwalten",
330            pref_desc:
331                "Wählen Sie aus, welche Cookies Sie akzeptieren möchten. Sie können Ihre Einstellungen jederzeit ändern.",
332            powered_by: "Betrieben von",
333        },
334        it: {
335            title: "Utilizziamo i cookie",
336            message:
337                "Utilizziamo i cookie per migliorare la tua esperienza sul nostro sito web. Navigando in questo sito, accetti il nostro utilizzo dei cookie.",
338            accept: "Accetta tutto",
339            reject: "Rifiuta tutto",
340            manage: "Gestisci preferenze",
341            save: "Salva preferenze",
342            withdraw: "Revocare il consenso",
343            cancel: "Annulla",
344            always_active: "Sempre attivo",
345            cookies_used: "Cookie utilizzati:",
346            privacy: "Informativa sulla privacy",
347            terms: "Termini e condizioni",
348            pref_title: "Gestisci le preferenze dei cookie",
349            pref_desc:
350                "Scegli quali cookie desideri accettare. Puoi modificare le tue preferenze in qualsiasi momento.",
351            powered_by: "Offerto da",
352        },
353        pt: {
354            title: "Usamos cookies",
355            message:
356                "Usamos cookies para melhorar sua experiência em nosso site. Ao navegar neste site, você concorda com nosso uso de cookies.",
357            accept: "Aceitar tudo",
358            reject: "Rejeitar tudo",
359            manage: "Gerenciar preferências",
360            save: "Salvar preferências",
361            withdraw: "Revogar consentimento",
362            cancel: "Cancelar",
363            always_active: "Sempre ativo",
364            cookies_used: "Cookies utilizados:",
365            privacy: "Política de Privacidade",
366            terms: "Termos e Condições",
367            pref_title: "Gerenciar preferências de cookies",
368            pref_desc:
369                "Escolha quais cookies você deseja aceitar. Você pode alterar suas preferências a qualquer momento.",
370            powered_by: "Desenvolvido por",
371        },
372        nl: {
373            title: "Wij gebruiken cookies",
374            message:
375                "Wij gebruiken cookies om uw ervaring op onze website te verbeteren. Door op deze website te surfen, gaat u akkoord met ons gebruik van cookies.",
376            accept: "Alles accepteren",
377            reject: "Alles weigeren",
378            manage: "Voorkeuren beheren",
379            save: "Voorkeuren opslaan",
380            withdraw: "Toestemming intrekken",
381            cancel: "Annuleren",
382            always_active: "Altijd actief",
383            cookies_used: "Gebruikte cookies:",
384            privacy: "Privacybeleid",
385            terms: "Algemene voorwaarden",
386            pref_title: "Cookievoorkeuren beheren",
387            pref_desc:
388                "Kies welke cookies u wilt accepteren. U kunt uw voorkeuren op elk moment wijzigen.",
389            powered_by: "Aangedreven door",
390        },
391        pl: {
392            title: "Używamy plików cookie",
393            message:
394                "Używamy plików cookie, aby poprawić Twoje doświadczenia na naszej stronie internetowej. Przeglądając tę stronę, zgadzasz się na nasze użycie plików cookie.",
395            accept: "Zaakceptuj wszystko",
396            reject: "Odrzuć wszystko",
397            manage: "Zarządzaj preferencjami",
398            save: "Zapisz preferencje",
399            withdraw: "Wycofaj zgodę",
400            cancel: "Anuluj",
401            always_active: "Zawsze aktywne",
402            cookies_used: "Używane pliki cookie:",
403            privacy: "Polityka prywatności",
404            terms: "Regulamin",
405            pref_title: "Zarządzaj preferencjami plików cookie",
406            pref_desc:
407                "Wybierz, które pliki cookie chcesz zaakceptować. Możesz zmienić swoje preferencje w dowolnym momencie.",
408            powered_by: "Obsługiwane przez",
409        },
410        ja: {
411            title: "Cookieを使用しています",
412            message:
413                "当ウェブサイトではCookieを使用して、お客様の体験を向上させています。このウェブサイトを閲覧することにより、Cookieの使用に同意するものとします。",
414            accept: "すべて同意",
415            reject: "すべて拒否",
416            manage: "設定を管理",
417            save: "設定を保存",
418            withdraw: "同意を撤回",
419            cancel: "キャンセル",
420            always_active: "常にアクティブ",
421            cookies_used: "使用Cookie:",
422            privacy: "プライバシーポリシー",
423            terms: "利用規約",
424            pref_title: "Cookie設定の管理",
425            pref_desc:
426                "受け入れるCookieを選択してください。設定はいつでも変更できます。",
427            powered_by: "提供元",
428        },
429        zh: {
430            title: "我们使用Cookie",
431            message:
432                "我们使用Cookie来改善您在我们网站上的体验。继续浏览本网站即表示您同意我们使用Cookie。",
433            accept: "全部接受",
434            reject: "全部拒绝",
435            manage: "管理偏好",
436            save: "保存偏好",
437            withdraw: "撤回同意",
438            cancel: "取消",
439            always_active: "始终启用",
440            cookies_used: "使用的Cookie:",
441            privacy: "隐私政策",
442            terms: "条款与条件",
443            pref_title: "管理Cookie偏好",
444            pref_desc: "选择您想要接受的Cookie。您可以随时更改偏好设置。",
445            powered_by: "技术支持",
446        },
447        ko: {
448            title: "쿠키를 사용합니다",
449            message:
450                "이 웹사이트에서는 사용자 경험 향상을 위해 쿠키를 사용합니다. 이 웹사이트를 탐색하면 쿠키 사용에 동의하는 것입니다.",
451            accept: "모두 수락",
452            reject: "모두 거부",
453            manage: "환경설정 관리",
454            save: "환경설정 저장",
455            withdraw: "동의 철회",
456            cancel: "취소",
457            always_active: "항상 활성",
458            cookies_used: "사용된 쿠키:",
459            privacy: "개인정보 처리방침",
460            terms: "이용약관",
461            pref_title: "쿠키 환경설정 관리",
462            pref_desc:
463                "수락할 쿠키를 선택하세요. 언제든지 환경설정을 변경할 수 있습니다.",
464            powered_by: "제공",
465        },
466        ru: {
467            title: "Мы используем файлы cookie",
468            message:
469                "Мы используем файлы cookie для улучшения вашего опыта на нашем сайте. Продолжая просматривать этот сайт, вы соглашаетесь на использование файлов cookie.",
470            accept: "Принять все",
471            reject: "Отклонить все",
472            manage: "Управление настройками",
473            save: "Сохранить настройки",
474            withdraw: "Отозвать согласие",
475            cancel: "Отмена",
476            always_active: "Всегда активен",
477            cookies_used: "Используемые файлы cookie:",
478            privacy: "Политика конфиденциальности",
479            terms: "Условия использования",
480            pref_title: "Управление настройками файлов cookie",
481            pref_desc:
482                "Выберите, какие файлы cookie вы хотите принять. Вы можете изменить свои настройки в любое время.",
483            powered_by: "Работает на",
484        },
485        ar: {
486            title: "نستخدم ملفات تعريف الارتباط",
487            message:
488                "نستخدم ملفات تعريف الارتباط لتحسين تجربتك على موقعنا. بتصفح هذا الموقع، فإنك توافق على استخدامنا لملفات تعريف الارتباط.",
489            accept: "قبول الكل",
490            reject: "رفض الكل",
491            manage: "إدارة التفضيلات",
492            save: "حفظ التفضيلات",
493            withdraw: "سحب الموافقة",
494            cancel: "إلغاء",
495            always_active: "نشط دائماً",
496            cookies_used: "ملفات تعريف الارتباط المستخدمة:",
497            privacy: "سياسة الخصوصية",
498            terms: "الشروط والأحكام",
499            pref_title: "إدارة تفضيلات ملفات تعريف الارتباط",
500            pref_desc:
501                "اختر ملفات تعريف الارتباط التي تريد قبولها. يمكنك تغيير تفضيلاتك في أي وقت.",
502            powered_by: "مدعوم من",
503        },
504        hi: {
505            title: "हम कुकीज़ का उपयोग करते हैं",
506            message:
507                "हम आपके अनुभव को बेहतर बनाने के लिए कुकीज़ का उपयोग करते हैं। इस वेबसाइट को ब्राà
507¤‰à¤œà¤¼ करके, आप हमारे कुकीज़ के उपयोग से सहमत हैं।",
508            accept: "सभी स्वीकार करें",
509            reject: "सभी अस्वीकार करें",
510            manage: "प्राथमिकताएँ प्रबंधित करें",
511            save: "प्राथमिकताएँ सहेजें",
512            withdraw: "सहमति वापस लें",
513            cancel: "रद्द करें",
514            always_active: "हमेशा सक्रिय",
515            cookies_used: "उपयोग की गई कुकीज़:",
516            privacy: "गोपनीयता नीति",
517            terms: "नियम और शर्तें",
518            pref_title: "कुकी प्राथमिकताएँ प्रबंधित करें",
519            pref_desc:
520                "चुनें कि आप कौन सी कुकीज़ स्वीकार करना चाहते हैं। आप किसी भी समय अपनी प्राथमिकताएँ बदल सकते हैं।",
521            powered_by: "द्वारा संचालित",
522        },
523        tr: {
524            title: "Çerezleri kullanıyoruz",
525            message:
526                "Web sitemizdeki deneyiminizi iyileştirmek için çerezleri kullanıyoruz. Bu web sitesini tarayarak çerez kullanımımızı kabul etmiş olursunuz.",
527            accept: "Tümünü kabul et",
528            reject: "Tümünü reddet",
529            manage: "Tercihleri yönet",
530            save: "Tercihleri kaydet",
531            withdraw: "Onayı geri çek",
532            cancel: "İptal",
533            always_active: "Her zaman aktif",
534            cookies_used: "Kullanılan çerezler:",
535            privacy: "Gizlilik Politikası",
536            terms: "Şartlar ve Koşullar",
537            pref_title: "Çerez tercihlerini yönet",
538            pref_desc:
539                "Kabul etmek istediğiniz çerezleri seçin. Tercihlerinizi istediğiniz zaman değiştirebilirsiniz.",
540            powered_by: "Tarafından desteklenmektedir",
541        },
542        sv: {
543            title: "Vi använder cookies",
544            message:
545                "Vi använder cookies för att förbättra din upplevelse på vår webbplats. Genom att surfa på denna webbplats samtycker du till vår användning av cookies.",
546            accept: "Acceptera alla",
547            reject: "Avvisa alla",
548            manage: "Hantera inställningar",
549            save: "Spara inställningar",
550            withdraw: "Återkalla samtycke",
551            cancel: "Avbryt",
552            always_active: "Alltid aktiv",
553            cookies_used: "Använda cookies:",
554            privacy: "Integritetspolicy",
555            terms: "Villkor",
556            pref_title: "Hantera cookie-inställningar",
557            pref_desc:
558                "Välj vilka cookies du vill acceptera. Du kan ändra dina inställningar när som helst.",
559            powered_by: "Drivs av",
560        },
561        da: {
562            title: "Vi bruger cookies",
563            message:
564                "Vi bruger cookies for at forbedre din oplevelse på vores hjemmeside. Ved at browse denne hjemmeside accepterer du vores brug af cookies.",
565            accept: "Accepter alle",
566            reject: "Afvis alle",
567            manage: "Administrer præferencer",
568            save: "Gem præferencer",
569            withdraw: "Træk samtykke tilbage",
570            cancel: "Annuller",
571            always_active: "Altid aktiv",
572            cookies_used: "Anvendte cookies:",
573            privacy: "Privatlivspolitik",
574            terms: "Vilkår og betingelser",
575            pref_title: "Administrer cookie-præferencer",
576            pref_desc:
577                "Vælg hvilke cookies du vil acceptere. Du kan ændre dine præferencer til enhver tid.",
578            powered_by: "Drevet af",
579        },
580        fi: {
581            title: "Käytämme evästeitä",
582            message:
583                "Käytämme evästeitä parantaaksemme kokemustasi verkkosivuillamme. Selaamalla tätä sivustoa hyväksyt evästeiden käytön.",
584            accept: "Hyväksy kaikki",
585            reject: "Hylkää kaikki",
586            manage: "Hallitse asetuksia",
587            save: "Tallenna asetukset",
588            withdraw: "Peru suostumus",
589            cancel: "Peruuta",
590            always_active: "Aina aktiivinen",
591            cookies_used: "Käytetyt evästeet:",
592            privacy: "Tietosuojakäytäntö",
593            terms: "Käyttöehdot",
594            pref_title: "Hallitse evästeasetuksia",
595            pref_desc:
596                "Valitse mitkä evästeet haluat hyväksyä. Voit muuttaa asetuksiasi milloin tahansa.",
597            powered_by: "Palvelun tarjoaa",
598        },
599        no: {
600            title: "Vi bruker informasjonskapsler",
601            message:
602                "Vi bruker informasjonskapsler for å forbedre din opplevelse på vår nettside. Ved å surfe på dette nettstedet samtykker du til vår bruk av informasjonskapsler.",
603            accept: "Godta alle",
604            reject: "Avvis alle",
605            manage: "Administrer innstillinger",
606            save: "Lagre innstillinger",
607            withdraw: "Trekk tilbake samtykke",
608            cancel: "Avbryt",
609            always_active: "Alltid aktiv",
610            cookies_used: "Brukte informasjonskapsler:",
611            privacy: "Personvernerklæring",
612            terms: "Vilkår og betingelser",
613            pref_title: "Administrer cookie-innstillinger",
614            pref_desc:
615                "Velg hvilke informasjonskapsler du vil godta. Du kan endre innstillingene dine når som helst.",
616            powered_by: "Drevet av",
617        },
618        cs: {
619            title: "Používáme soubory cookie",
620            message:
621                "Používáme soubory cookie ke zlepšení vašeho zážitku na našich webových stránkách. Prohlížením těchto stránek souhlasíte s naším používáním souborů cookie.",
622            accept: "Přijmout vše",
623            reject: "Odmítnout vše",
624            manage: "Spravovat předvolby",
625            save: "Uložit předvolby",
626            withdraw: "Odvolat souhlas",
627            cancel: "Zrušit",
628            always_active: "Vždy aktivní",
629            cookies_used: "Použité soubory cookie:",
630            privacy: "Zásady ochrany osobních údajů",
631            terms: "Obchodní podmínky",
632            pref_title: "Správa předvoleb souborů cookie",
633            pref_desc:
634                "Vyberte, které soubory cookie chcete přijmout. Své předvolby můžete kdykoli změnit.",
635            powered_by: "Provozuje",
636        },
637        el: {
638            title: "Χρησιμοποιούμε cookies",
639            message:
640                "Χρησιμοποιούμε cookies για να βελτιώσουμε την εμπειρία σας. Περιηγούμενοι σε αυτόν τον ιστότοπο, συμφωνείτε με τη χρήση cookies.",
641            accept: "Αποδοχή όλων",
642            reject: "Απόρριψη όλων",
643            manage: "Διαχείριση προτιμήσεων",
644            save: "Αποθήκευση",
645            withdraw: "Ανάκληση συγκατάθεσης",
646            cancel: "Ακύρωση",
647            always_active: "Πάντα ενεργό",
648            cookies_used: "Cookies που χρησιμοποιούνται:",
649            privacy: "Πολιτική Απορρήτου",
650            terms: "Όροι & Προϋποθέσεις",
651            pref_title: "Διαχείριση προτιμήσεων cookies",
652            pref_desc:
653                "Επιλέξτε ποια cookies θέλετε να αποδεχτείτε. Μπορείτε να αλλάξετε τις προτιμήσεις σας ανά πάσα στιγμή.",
654            powered_by: "Τροφοδοτείται από",
655        },
656        hu: {
657            title: "Sütiket használunk",
658            message:
659                "Sütiket használunk a felhasználói élmény javítása érdekében. Az oldal böngészésével elfogadja a sütik használatát.",
660            accept: "Összes elfogadása",
661            reject: "Összes elutasítása",
662            manage: "Beállítások kezelése",
663            save: "Beállítások mentése",
664            withdraw: "Hozzájárulás visszavonása",
665            cancel: "Mégse",
666            always_active: "Mindig aktív",
667            cookies_used: "Használt sütik:",
668            privacy: "Adatvédelmi irányelvek",
669            terms: "Felhasználási feltételek",
670            pref_title: "Süti-beállítások kezelése",
671            pref_desc:
672                "Válassza ki, mely sütiket szeretné elfogadni. Bármikor módosíthatja beállításait.",
673            powered_by: "Üzemelteti",
674        },
675        ro: {
676            title: "Folosim cookie-uri",
677            message:
678                "Folosim cookie-uri pentru a vă îmbunătăți experiența pe site-ul nostru. Navigând pe acest site, sunteți de acord cu utilizarea cookie-urilor.",
679            accept: "Acceptă tot",
680            reject: "Respinge tot",
681            manage: "Gestionează preferințele",
682            save: "Salvează preferințele",
683            withdraw: "Retrage consimțământul",
684            cancel: "Anulează",
685            always_active: "Mereu activ",
686            cookies_used: "Cookie-uri utilizate:",
687            privacy: "Politica de confidențialitate",
688            terms: "Termeni și condiții",
689            pref_title: "Gestionează preferințele cookie",
690            pref_desc:
691                "Alegeți ce cookie-uri doriți să acceptați. Puteți schimba preferințele oricând.",
692            powered_by: "Oferit de",
693        },
694        uk: {
695            title: "Ми використовуємо файли cookie",
696            message:
697                "Ми використовуємо файли cookie для покращення вашого досвіду. Переглядаючи цей сайт, ви погоджуєтеся з використанням файлів cookie.",
698            accept: "Прийняти всі",
699            reject: "Відхилити всі",
700            manage: "Керувати налаштуваннями",
701            save: "Зберегти налаштування",
702            withdraw: "Відкликати згоду",
703            cancel: "Скасувати",
704            always_active: "Завжди активний",
705            cookies_used: "Використовувані файли cookie:",
706            privacy: "Політика конфіденційності",
707            terms: "Умови використання",
708            pref_title: "Керування налаштуваннями файлів cookie",
709            pref_desc:
710                "Оберіть, які файли cookie ви хочете прийняти. Ви можете змінити налаштування в будь-який час.",
711            powered_by: "Працює на",
712        },
713        th: {
714            title: "เราใช้คุกกี้",
715            message:
716                "เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์ของคุณบนเว็บไซต์ของเรา การเข้าชมเว็บไซต์นี้แสดงว่าคุณยินยอมให้เราใช้คุกกี้",
717            accept: "ยอมรับทั้งหมด",
718            reject: "ปฏิเสธทั้งหมด",
719            manage: "จัดการการตั้งค่า",
720            save: "บันทึกการตั้งค่า",
721            withdraw: "ถอนความยินยอม",
722            cancel: "ยกเลิก",
723            always_active: "เปิดใช้งานตลอด",
724            cookies_used: "คุกกี้ที่ใช้:",
725            privacy: "นโยบายความเป็นส่วนตัว",
726            terms: "ข้อกำหนดและเงื่อนไข",
727            pref_title: "จัดการการตั้งค่าคุกกี้",
728            pref_desc:
729                "เลือกคุกกี้ที่คุณต้องการยอมรับ คุณสามารถเปลี่ยนการตั้งค่าได้ตลอดเวลา",
730            powered_by: "ขับเคลื่อนโดย",
731        },
732        vi: {
733            title: "Chúng tôi sử dụng cookie",
734            message:
735                "Chúng tôi sử dụng cookie để cải thiện trải nghiệm của bạn trên trang web. Bằng cách duyệt trang web này, bạn đồng ý với việc sử dụng cookie của chúng tôi.",
736            accept: "Chấp nhận tất cả",
737            reject: "Từ chối tất cả",
738            manage: "Quản lý tùy chọn",
739            save: "Lưu tùy chọn",
740            withdraw: "Rút lại sự đồng ý",
741            cancel: "Há»§y",
742            always_active: "Luôn hoạt động",
743            cookies_used: "Cookie được sử dụng:",
744            privacy: "Chính sách bảo mật",
745            terms: "Điều khoản & Điều kiện",
746            pref_title: "Quản lý tùy chọn cookie",
747            pref_desc:
748                "Chọn cookie bạn muốn chấp nhận. Bạn có thể thay đổi tùy chọn bất cứ lúc nào.",
749            powered_by: "Được cung cấp bởi",
750        },
751        id: {
752            title: "Kami menggunakan cookie",
753            message:
754                "Kami menggunakan cookie untuk meningkatkan pengalaman Anda di situs web kami. Dengan menjelajahi situs web ini, Anda menyetujui penggunaan cookie kami.",
755            accept: "Terima Semua",
756            reject: "Tolak Semua",
757            manage: "Kelola Preferensi",
758            save: "Simpan Preferensi",
759            withdraw: "Tarik Persetujuan",
760            cancel: "Batal",
761            always_active: "Selalu aktif",
762            cookies_used: "Cookie yang digunakan:",
763            privacy: "Kebijakan Privasi",
764            terms: "Syarat & Ketentuan",
765            pref_title: "Kelola Preferensi Cookie",
766            pref_desc:
767                "Pilih cookie yang ingin Anda terima. Anda dapat mengubah preferensi kapan saja.",
768            powered_by: "Didukung oleh",
769        },
770        ms: {
771            title: "Kami menggunakan kuki",
772            message:
773                "Kami menggunakan kuki untuk meningkatkan pengalaman anda di laman web kami. Dengan melayari laman web ini, anda bersetuju dengan penggunaan kuki kami.",
774            accept: "Terima Semua",
775            reject: "Tolak Semua",
776            manage: "Urus Keutamaan",
777            save: "Simpan Keutamaan",
778            withdraw: "Tarik Balik Persetujuan",
779            cancel: "Batal",
780            always_active: "Sentiasa aktif",
781            cookies_used: "Kuki yang digunakan:",
782            privacy: "Dasar Privasi",
783            terms: "Terma & Syarat",
784            pref_title: "Urus Keutamaan Kuki",
785            pref_desc:
786                "Pilih kuki yang anda ingin terima. Anda boleh menukar keutamaan anda pada bila-bila masa.",
787            powered_by: "Dikuasakan oleh",
788        },
789    };
790
791    // ──────────────────────────────────────────────
792    // State
793    // ──────────────────────────────────────────────
794
795    var bannerConfig = null;
796    var serverTranslations = null; // Translations from API (overrides built-in)
797    var currentConsent = null;
798    var isInitialized = false;
799    var preferenceCenterEl = null;
800    var sidebarOverlayEl = null;
801    var consentWallEl = null;
802    var visitorId = "";
803    var currentLang = "en";
804    var consentCallbacks = []; // v4.1.0: onConsentChange listeners
805    var scriptObserver = null; // v4.1.0: MutationObserver for dynamic scripts
806    var dntEnabled = false; // v4.1.0: Do Not Track detected
807    var gpcEnabled = false; // v4.1.0: Global Privacy Control detected
808    var withdrawInProgress = false;
809    var CONFIG_CACHE_KEY = "ff_cc_config_" + CONFIG_ID; // v4.1.0: Config caching
810
811    // ──────────────────────────────────────────────
812    // Initialization
813    // ──────────────────────────────────────────────
814
815    function init() {
816        if (isInitialized) return;
817        isInitialized = true;
818
819        injectStyles();
820        visitorId = getOrCreateVisitorId();
821        currentLang = detectLanguage();
822        detectPrivacySignals(); // v4.1.0: DNT & GPC detection
823
824        var visitorCountry = getCookieValue("ff_country") || "IN";
825
826        try {
827            currentConsent = loadConsent();
828
829            if (currentConsent && !isConsentExpired(currentConsent)) {
830                applyConsent(currentConsent);
831                initGoogleConsentModeFromLocal(currentConsent);
832                scheduleImpression("returning_visitor");
833                startScriptObserver(); // v4.1.0: Watch for dynamic scripts
834                fetchConfigurationCached(visitorCountry).then(function (cfg) {
835                    bannerConfig = cfg;
836                    if (cfg) {
837                        applyThemePreset();
838                        injectCustomCSS();
839                        if (cfg.show_floating_button !== false)
840                            createFloatingButton();
841                        bindCustomRevisitButton();
842                        // v4.1.0: Re-apply consent now that config is loaded (for slug resolution)
843                        applyConsent(currentConsent);
844                    }
845                });
846                return;
847            }
848
849            fetchConfigurationCached(visitorCountry).then(function (cfg) {
850                if (!cfg) {
851                    console.warn("[FF-Consent] Could not load configuration");
852                    return;
853                }
854                bannerConfig = cfg;
855                applyThemePreset();
856                injectCustomCSS();
857                setGoogleConsentModeDefaults();
858                blockIframes();
859                startScriptObserver(); // v4.1.0: Watch for dynamic scripts
860
861                // v4.1.0: If DNT/GPC is enabled and config respects it, auto-reject
862                if ((dntEnabled || gpcEnabled) && cfg.respect_dnt_gpc) {
863                    handleRejectAll();
864                    return;
865                }
866
867                showConsentUI();
868                scheduleImpression("banner_shown");
869            });
870        } catch (err) {
871            console.error("[FF-Consent] Init error:", err);
872        }
873    }
874
875    // ──────────────────────────────────────────────
876    // v4.1.0: DNT & GPC Privacy Signal Detection
877    // ──────────────────────────────────────────────
878
879    function detectPrivacySignals() {
880        // Do Not Track
881        dntEnabled =
882            navigator.doNotTrack === "1" ||
883            navigator.doNotTrack === "yes" ||
884            window.doNotTrack === "1";
885        // Global Privacy Control (https://globalprivacycontrol.org/)
886        gpcEnabled = !!navigator.globalPrivacyControl;
887
888        if (dntEnabled) console.log("[FF-Consent] DNT signal detected");
889        if (gpcEnabled) console.log("[FF-Consent] GPC signal detected");
890    }
891
892    // ──────────────────────────────────────────────
893    // v4.1.0: Configuration Caching
894    // ──────────────────────────────────────────────
895
896    function fetchConfigurationCached(visitorCountry) {
897        console.log("first", visitorCountry);
898        // Try sessionStorage cache first (valid for 5 minutes)
899        try {
900            var cached = sessionStorage.getItem(CONFIG_CACHE_KEY);
901            if (cached) {
902                var parsed = JSON.parse(cached);
903                if (
904                    parsed._cacheTime &&
905                    Date.now() - parsed._cacheTime < 300000
906                ) {
907                    if (parsed.translations)
908                        serverTranslations = parsed.translations;
909                    delete parsed._cacheTime;
910                    var cfg = normalizeConfig(parsed);
911                    if (cfg) return Promise.resolve(cfg);
912                }
913            }
914        } catch (e) { }
915        // Fetch fresh from API
916        return fetchConfiguration(visitorCountry).then(function (cfg) {
917            console.log("second", visitorCountry);
918
919            // Cache the raw response
920            if (cfg) {
921                try {
922                    var raw = JSON.parse(JSON.stringify(cfg));
923                    raw._cacheTime = Date.now();
924                    sessionStorage.setItem(
925                        CONFIG_CACHE_KEY,
926                        JSON.stringify(raw),
927                    );
928                } catch (e) { }
929            }
930            return cfg;
931        });
932    }
933
934    // ──────────────────────────────────────────────
935    // Language Detection & Translation
936    // ──────────────────────────────────────────────
937
938    function detectLanguage() {
939        if (userConfig.language && TRANSLATIONS[userConfig.language])
940            return userConfig.language;
941        var navLang = (
942            navigator.language ||
943            navigator.userLanguage ||
944            "en"
945        ).toLowerCase();
946        var short = navLang.split("-")[0];
947        if (TRANSLATIONS[short]) return short;
948        return "en";
949    }
950
951    /**
952     * Get translated string.
953     * Priority: server translations (from API) → built-in TRANSLATIONS → English fallback
954     */
955    function t(key) {
956        var lang =
957            bannerConfig && bannerConfig.language
958                ? bannerConfig.language
959                : currentLang;
960
961        // 1. Server-side translations for current language
962        if (serverTranslations && serverTranslations[lang]) {
963            var st = serverTranslations[lang];
964            // Map server content keys → widget translation keys
965            if (key === "title" && st.title) return st.title;
966            if (key === "message" && st.description) return st.description;
967            if (key === "accept" && st.accept_text) return st.accept_text;
968            if (key === "reject" && st.reject_text) return st.reject_text;
969            if (key === "manage" && st.settings_text) return st.settings_text;
970            if (key === "save" && st.save_preferences_text)
971                return st.save_preferences_text;
972            if (key === "withdraw" && st.withdraw_text) return st.withdraw_text;
973            if (key === "pref_title" && st.preference_center_title)
974                return st.preference_center_title;
975            if (key === "pref_desc" && st.preference_center_description)
976                return st.preference_center_description;
977        }
978
979        // 2. Built-in translations
980        var trans = TRANSLATIONS[lang] || TRANSLATIONS["en"];
981        return trans[key] || TRANSLATIONS["en"][key] || key;
982    }
983
984    // ──────────────────────────────────────────────
985    // Visitor ID
986    // ──────────────────────────────────────────────
987
988    function getOrCreateVisitorId() {
989        var id = null;
990        try {
991            id = localStorage.getItem(VISITOR_COOKIE);
992        } catch (e) { }
993        if (!id) id = getCookieValue(VISITOR_COOKIE);
994        if (!id) {
995            id = "fv_" + generateId(20);
996            try {
997                localStorage.setItem(VISITOR_COOKIE, id);
998            } catch (e) { }
999            setCookie(VISITOR_COOKIE, id, 365 * 2);
1000        }
1001        return id;
1002    }
1003
1004    function generateId(len) {
1005        var chars =
1006            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1007        var result = "";
1008        if (typeof crypto !== "undefined" && crypto.getRandomValues) {
1009            var arr = new Uint8Array(len);
1010            crypto.getRandomValues(arr);
1011            for (var i = 0; i < len; i++)
1012                result += chars[arr[i] % chars.length];
1013        } else {
1014            for (var j = 0; j < len; j++)
1015                result += chars[Math.floor(Math.random() * chars.length)];
1016        }
1017        return result;
1018    }
1019
1020    // ──────────────────────────────────────────────
1021    // Configuration Fetching
1022    // ──────────────────────────────────────────────
1023
1024    function fetchConfiguration(country) {
1025        var url = `${API_URL}/${encodeURIComponent(CONFIG_ID)}`;
1026        console.log(country);
1027
1028        if (country) {
1029            url += "?country=" + encodeURIComponent(country);
1030        }
1031
1032        return httpGet(url)
1033            .then(function (data) {
1034                if (!data) return null;
1035                // Store server translations before normalizing
1036                if (data.translations) {
1037                    serverTranslations = data.translations;
1038                }
1039                return normalizeConfig(data);
1040            })
1041            .catch(function (err) {
1042                console.error("[FF-Consent] Config fetch failed:", err);
1043                return null;
1044            });
1045    }
1046
1047    /**
1048     * Normalize API response → internal config object.
1049     *
1050     * FIX v4.0.1:
1051     *  - Maps 'modal' layout → 'popup' (widget CSS has no .ff-cc-modal layout)
1052     *  - Reads 'language' OR 'default_language' (API sends both now)
1053     *  - Content fields support BOTH API-style and DB-style names
1054     *  - All v4 fields included (theme_preset, btn styles, revisit, etc.)
1055     */
1056    function normalizeConfig(raw) {
1057        if (!raw || raw.error) return null;
1058        var content = raw.content || {};
1059        var layout = raw.layout || "box";
1060        if (VALID_LAYOUTS.indexOf(layout) === -1) layout = "box";
1061
1062        // FIX: 'modal' layout has no dedicated CSS class — map to 'popup'
1063        if (layout === "modal") layout = "popup";
1064
1065        var position = raw.position || "bottom";
1066        if (VALID_POSITIONS.indexOf(position) === -1) position = "bottom";
1067
1068        return {
1069            widget_id: raw.widget_id,
1070            domain: raw.domain,
1071            position: position,
1072            layout: layout,
1073            theme: raw.theme || "light",
1074            theme_preset: raw.theme_preset || null,
1075            primary_color: raw.primary_color || "#2563EB",
1076            secondary_color: raw.secondary_color || "#1E40AF",
1077            background_color: raw.background_color || "#ffffff",
1078            text_color: raw.text_color || "#1F2937",
1079            title_color: raw.title_color || null,
1080            separator_color: raw.separator_color || "#E5E7EB",
1081            footer_border_color: raw.footer_border_color || "#E5E7EB",
1082            banner_radius: raw.banner_radius != null ? raw.banner_radius : null,
1083            auto_block_cookies: !!raw.auto_block_cookies,
1084            show_badge: !!raw.show_badge,
1085            show_floating_button: raw.show_floating_button !== false,
1086            consent_duration_days: raw.consent_duration_days || 365,
1087            country_code: raw.country_code,
1088            applicable_regulations: raw.applicable_regulations || [],
1089            require_explicit_consent: raw.require_explicit_consent !== false,
1090            show_reject_button: raw.show_reject_button !== false,
1091            enable_google_consent_mode: !!raw.enable_google_consent_mode,
1092            enable_iab_tcf: !!raw.enable_iab_tcf,
1093            show_manage_preferences: raw.show_manage_preferences !== false,
1094            preference_center_type: raw.preference_center_type || "modal",
1095            preference_center_title: raw.preference_center_title || null,
1096            preference_center_description:
1097                raw.preference_center_description || null,
1098            buttons_flip: !!raw.buttons_flip,
1099            disable_website_interaction: !!raw.disable_website_interaction,
1100            hide_powered_by: !!raw.hide_powered_by,
1101            // FIX: API sends 'language' AND 'default_language' — read either
1102            language: raw.language || raw.default_language || null,
1103            auto_detect_language: raw.auto_detect_language !== false,
1104            custom_css: raw.custom_css || "",
1105            revisit_button_selector: raw.revisit_button_selector || null,
1106            revisit_button_position:
1107                raw.revisit_button_position || "bottom-left",
1108            revisit_button_text: raw.revisit_button_text || null,
1109            floating_button_position:
1110                raw.floating_button_position || "bottom-left",
1111            floating_image_button: raw.floating_image_button || null,
1112            block_iframes: !!raw.block_iframes,
1113            blocked_iframe_message:
1114                raw.blocked_iframe_message ||
1115                "This content requires cookies to be accepted.",
1116            respect_dnt_gpc: !!raw.respect_dnt_gpc, // v4.1.0: Respect DNT/GPC signals
1117            accept_btn_style: raw.accept_btn_style || null,
1118            reject_btn_style: raw.reject_btn_style || null,
1119            manage_btn_style: raw.manage_btn_style || null,
1120            // FIX: Content fields — support BOTH API-style AND DB-style names
1121            title: content.title || content.banner_title || raw.title || null,
1122            message:
1123                content.description ||
1124                content.banner_description ||
1125                raw.message ||
1126                null,
1127            accept_button_text:
1128                content.accept_text ||
1129                content.accept_button_text ||
1130                raw.accept_button_text ||
1131                null,
1132            reject_button_text:
1133                content.reject_text ||
1134                content.reject_button_text ||
1135                raw.reject_button_text ||
1136                null,
1137            manage_button_text:
1138                content.settings_text ||
1139                content.settings_button_text ||
1140                raw.manage_button_text ||
1141                null,
1142            privacy_policy_url:
1143                content.privacy_url ||
1144                content.privacy_policy_url ||
1145                raw.privacy_policy_url ||
1146                null,
1147            terms_url: content.terms_url || raw.terms_url || null,
1148            categories: (raw.categories || []).map(function (cat) {
1149                return {
1150                    id: cat.id,
1151                    slug: cat.slug,
1152                    name: cat.name,
1153                    description: cat.description || "",
1154                    is_essential: !!cat.is_essential,
1155                    is_enabled_by_default: !!cat.is_enabled_by_default,
1156                    cookies: cat.cookies || [],
1157                };
1158            }),
1159        };
1160    }
1161
1162    // ──────────────────────────────────────────────
1163    // Theme Preset Application
1164    // ──────────────────────────────────────────────
1165
1166    function applyThemePreset() {
1167        if (!bannerConfig || !bannerConfig.theme_preset) return;
1168        var preset = THEME_PRESETS[bannerConfig.theme_preset];
1169        if (!preset) return;
1170
1171        var fields = [
1172            "primary_color",
1173            "secondary_color",
1174            "background_color",
1175            "text_color",
1176            "title_color",
1177            "separator_color",
1178            "footer_border_color",
1179        ];
1180        fields.forEach(function (f) {
1181            // Only apply preset if config doesn't have a custom value
1182            if (!bannerConfig[f]) {
1183                bannerConfig[f] = preset[f];
1184            }
1185        });
1186        if (!bannerConfig.accept_btn_style)
1187            bannerConfig.accept_btn_style = preset.accept_btn;
1188        if (!bannerConfig.reject_btn_style)
1189            bannerConfig.reject_btn_style = preset.reject_btn;
1190        if (!bannerConfig.manage_btn_style)
1191            bannerConfig.manage_btn_style = preset.manage_btn;
1192    }
1193
1194    // ──────────────────────────────────────────────
1195    // Custom CSS Injection
1196    // ──────────────────────────────────────────────
1197
1198    function injectCustomCSS() {
1199        if (!bannerConfig || !bannerConfig.custom_css) return;
1200        var existing = document.getElementById("ff-cc-custom-css");
1201        if (existing) existing.parentNode.removeChild(existing);
1202        var style = document.createElement("style");
1203        style.id = "ff-cc-custom-css";
1204        style.textContent = bannerConfig.custom_css;
1205        (document.head || document.getElementsByTagName("head")[0]).appendChild(
1206            style,
1207        );
1208    }
1209
1210    // ──────────────────────────────────────────────
1211    // Consent UI — Layout Router
1212    // ──────────────────────────────────────────────
1213
1214    function showConsentUI() {
1215        var existing = document.getElementById("ff-cookie-consent");
1216        if (existing) existing.parentNode.removeChild(existing);
1217        removeSidebarOverlay();
1218        removeConsentWall();
1219
1220        var el = buildConsentDOM();
1221        document.body.appendChild(el);
1222
1223        if (bannerConfig.layout === "sidebar") showSidebarOverlay();
1224        if (bannerConfig.disable_website_interaction) showConsentWall();
1225
1226        raf(function () {
1227            el.classList.add("ff-cc-visible");
1228        });
1229    }
1230
1231    function btnStyle(type) {
1232        var c = bannerConfig;
1233        var s = c[type + "_btn_style"];
1234        if (!s) {
1235            if (type === "accept")
1236                return (
1237                    "background:" +
1238                    esc(c.primary_color) +
1239                    ";color:#fff;border-color:" +
1240                    esc(c.primary_color) +
1241                    ";"
1242                );
1243            if (type === "reject")
1244                return (
1245                    "background:" +
1246                    esc(c.secondary_color) +
1247                    ";color:#fff;border-color:" +
1248                    esc(c.secondary_color) +
1249                    ";"
1250                );
1251            if (type === "manage")
1252                return (
1253                    "background:transparent;color:" +
1254                    esc(c.primary_color) +
1255                    ";border-color:" +
1256                    esc(c.primary_color) +
1257                    ";"
1258                );
1259        }
1260        return (
1261            "background:" +
1262            esc(s.bg) +
1263            ";color:" +
1264            esc(s.color) +
1265            ";border-color:" +
1266            esc(s.border) +
1267            ";" +
1268            "--ff-btn-hover-bg:" +
1269            esc(s.hover_bg) +
1270            ";--ff-btn-hover-color:" +
1271            esc(s.hover_color) +
1272            ";--ff-btn-hover-border:" +
1273            esc(s.hover_border) +
1274            ";"
1275        );
1276    }
1277
1278    function buildConsentDOM() {
1279        var c = bannerConfig;
1280        var titleColor = c.title_color || c.text_color;
1281
1282        var wrapper = document.createElement("div");
1283        wrapper.id = "ff-cookie-consent";
1284        wrapper.setAttribute("role", "dialog");
1285        wrapper.setAttribute("aria-label", "Cookie Consent");
1286
1287        var classes = ["ff-cc-wrapper", "ff-cc-" + c.layout];
1288        var pos = c.position;
1289        classes.push("ff-cc-pos-" + pos);
1290        if (["bottom", "top", "left", "right", "center"].indexOf(pos) !== -1) {
1291            classes.push("ff-cc-" + pos);
1292        }
1293        if (c.theme === "dark") classes.push("ff-cc-theme-dark");
1294        if (c.buttons_flip) classes.push("ff-cc-buttons-flip");
1295
1296        wrapper.className = classes.join(" ");
1297
1298        var radiusStyle =
1299            c.banner_radius != null
1300                ? "border-radius:" + parseInt(c.banner_radius, 10) + "px;"
1301                : "";
1302
1303        var html = "";
1304        html +=
1305            '<div class="ff-cc-container" style="background:' +
1306            esc(c.background_color) +
1307            ";color:" +
1308            esc(c.text_color) +
1309            ";" +
1310            radiusStyle +
1311            '">';
1312
1313        html += '<div class="ff-cc-content">';
1314        html +=
1315            '  <h3 class="ff-cc-title" style="color:' +
1316            esc(titleColor) +
1317            ';">' +
1318            esc(c.title || t("title")) +
1319            "</h3>";
1320        html +=
1321            '  <p class="ff-cc-description">' +
1322            esc(c.message || t("message")) +
1323            "</p>";
1324
1325        if (c.privacy_policy_url || c.terms_url) {
1326            html += '<div class="ff-cc-links">';
1327            if (c.privacy_policy_url)
1328                html +=
1329                    '<a href="' +
1330                    esc(c.privacy_policy_url) +
1331                    '" target="_blank" rel="noopener noreferrer" style="color:' +
1332                    esc(c.primary_color) +
1333                    ';">' +
1334                    esc(t("privacy")) +
1335                    "</a>";
1336            if (c.terms_url)
1337                html +=
1338                    '<a href="' +
1339                    esc(c.terms_url) +
1340                    '" target="_blank" rel="noopener noreferrer" style="color:' +
1341                    esc(c.primary_color) +
1342                    ';">' +
1343                    esc(t("terms")) +
1344                    "</a>";
1345            html += "</div>";
1346        }
1347        html += "</div>";
1348
1349        var buttons = [];
1350        buttons.push(
1351            '<button class="ff-cc-btn ff-cc-btn-accept" data-action="accept" style="' +
1352            btnStyle("accept") +
1353            '">' +
1354            esc(c.accept_button_text || t("accept")) +
1355            "</button>",
1356        );
1357        if (c.show_reject_button) {
1358            buttons.push(
1359                '<button class="ff-cc-btn ff-cc-btn-reject" data-action="reject" style="' +
1360                btnStyle("reject") +
1361                '">' +
1362                esc(c.reject_button_text || t("reject")) +
1363                "</button>",
1364            );
1365        }
1366        if (c.show_manage_preferences) {
1367            buttons.push(
1368                '<button class="ff-cc-btn ff-cc-btn-manage" data-action="manage" style="' +
1369                btnStyle("manage") +
1370                '">' +
1371                esc(c.manage_button_text || t("manage")) +
1372                "</button>",
1373            );
1374        }
1375
1376        if (c.buttons_flip) buttons.reverse();
1377
1378        html += '<div class="ff-cc-actions">' + buttons.join("") + "</div>";
1379
1380        if (!c.hide_powered_by) {
1381            html +=
1382                '<div class="ff-cc-powered-by">' +
1383                t("powered_by") +
1384                " <strong>FlexiFunnels</strong></div>";
1385        }
1386        html += "</div>";
1387
1388        wrapper.innerHTML = html;
1389
1390        addEvent(wrapper, "click", function (e) {
1391            var btn = closest(e.target, "[data-action]");
1392            if (!btn) return;
1393            var action = btn.getAttribute("data-action");
1394            if (action === "accept") handleAcceptAll();
1395            else if (action === "reject") handleRejectAll();
1396            else if (action === "manage") showPreferenceCenter();
1397        });
1398
1399        return wrapper;
1400    }
1401
1402    function hideConsentUI() {
1403        var el = document.getElementById("ff-cookie-consent");
1404        if (!el) return;
1405        el.classList.remove("ff-cc-visible");
1406        removeSidebarOverlay();
1407        removeConsentWall();
1408        setTimeout(function () {
1409            if (el.parentNode) el.parentNode.removeChild(el);
1410        }, 400);
1411    }
1412
1413    // ──────────────────────────────────────────────
1414    // Consent Wall (Disable Website Interaction)
1415    // ──────────────────────────────────────────────
1416
1417    function showConsentWall() {
1418        removeConsentWall();
1419        var wall = document.createElement("div");
1420        wall.id = "ff-cc-consent-wall";
1421        wall.className = "ff-cc-consent-wall";
1422        // v4.1.0: Block Tab navigation through the wall
1423        wall.setAttribute("tabindex", "-1");
1424        addEvent(wall, "focus", function () {
1425            var banner = document.getElementById("ff-cookie-consent");
1426            if (banner) {
1427                var firstBtn = banner.querySelector("button");
1428                if (firstBtn) firstBtn.focus();
1429            }
1430        });
1431        document.body.appendChild(wall);
1432        consentWallEl = wall;
1433        raf(function () {
1434            wall.classList.add("ff-cc-visible");
1435        });
1436    }
1437
1438    function removeConsentWall() {
1439        if (consentWallEl && consentWallEl.parentNode) {
1440            consentWallEl.classList.remove("ff-cc-visible");
1441            var el = consentWallEl;
1442            consentWallEl = null;
1443            setTimeout(function () {
1444                if (el.parentNode) el.parentNode.removeChild(el);
1445            }, 400);
1446        }
1447        var existing = document.getElementById("ff-cc-consent-wall");
1448        if (existing) existing.parentNode.removeChild(existing);
1449    }
1450
1451    // ──────────────────────────────────────────────
1452    // Sidebar Overlay
1453    // ──────────────────────────────────────────────
1454
1455    function showSidebarOverlay() {
1456        removeSidebarOverlay();
1457        var overlay = document.createElement("div");
1458        overlay.id = "ff-cc-sidebar-overlay";
1459        overlay.className = "ff-cc-sidebar-overlay";
1460        document.body.appendChild(overlay);
1461        sidebarOverlayEl = overlay;
1462        raf(function () {
1463            overlay.classList.add("ff-cc-visible");
1464        });
1465    }
1466
1467    function removeSidebarOverlay() {
1468        if (sidebarOverlayEl && sidebarOverlayEl.parentNode) {
1469            sidebarOverlayEl.classList.remove("ff-cc-visible");
1470            var el = sidebarOverlayEl;
1471            sidebarOverlayEl = null;
1472            setTimeout(function () {
1473                if (el.parentNode) el.parentNode.removeChild(el);
1474            }, 400);
1475        }
1476        var existing = document.getElementById("ff-cc-sidebar-overlay");
1477        if (existing && existing !== sidebarOverlayEl)
1478            existing.parentNode.removeChild(existing);
1479    }
1480
1481    // ──────────────────────────────────────────────
1482    // Preference Center (Modal)
1483    // ──────────────────────────────────────────────
1484
1485    function showPreferenceCenter() {
1486        removePreferenceCenter();
1487        var c = bannerConfig;
1488
1489        var modal = document.createElement("div");
1490        modal.id = "ff-pc-modal";
1491        modal.className =
1492            "ff-cc-modal" + (c.theme === "dark" ? " ff-cc-theme-dark" : "");
1493        modal.setAttribute("role", "dialog");
1494        modal.setAttribute("aria-modal", "true");
1495        modal.setAttribute(
1496            "aria-label",
1497            c.preference_center_title || t("pref_title"),
1498        );
1499
1500        var html = "";
1501        html += '<div class="ff-cc-modal-overlay"></div>';
1502        html += '<div class="ff-cc-modal-content">';
1503        html += '  <div class="ff-cc-modal-header">';
1504        html +=
1505            "    <h3>" +
1506            esc(c.preference_center_title || t("pref_title")) +
1507            "</h3>";
1508        html +=
1509            '    <button class="ff-cc-modal-close" aria-label="Close" data-action="close">&times;</button>';
1510        html += "  </div>";
1511        html += '  <div class="ff-cc-modal-body">';
1512        html +=
1513            "    <p>" +
1514            esc(c.preference_center_description || t("pref_desc")) +
1515            "</p>";
1516        html +=
1517            '    <div class="ff-cc-categories">' +
1518            renderCategories() +
1519            "</div>";
1520        html += "  </div>";
1521        html +=
1522            '  <div class="ff-cc-modal-footer" style="border-top-color:' +
1523            esc(c.footer_border_color || c.separator_color || "#E5E7EB") +
1524            ';">';
1525        html +=
1526            '    <button class="ff-cc-btn ff-cc-btn-reject" data-action="pc-reject" style="' +
1527            btnStyle("reject") +
1528            '">' +
1529            esc(c.reject_button_text || t("reject")) +
1530            "</button>";
1531        html +=
1532            '    <button class="ff-cc-btn ff-cc-btn-accept" data-action="pc-accept" style="' +
1533            btnStyle("accept") +
1534            '">' +
1535            esc(c.accept_button_text || t("accept")) +
1536            "</button>";
1537        html +=
1538            '    <button class="ff-cc-btn-save" data-action="save" style="background:' +
1539            esc(c.primary_color) +
1540            ';">' +
1541            esc(t("save")) +
1542            "</button>";
1543
1544        // ── Withdraw Consent button — only shown when user has active consent ──
1545        if (currentConsent && !isConsentExpired(currentConsent)) {
1546            html +=
1547                '<button class="ff-cc-btn ff-cc-btn-withdraw" data-action="withdraw" style="' +
1548                "background:transparent;color:#DC2626;border:1px solid #DC2626;" +
1549                "width:100%;font-size:12px;padding:7px 14px;" +
1550                "border-radius:8px;cursor:pointer;transition:all .2s;" +
1551                "--ff-btn-hover-bg:#FEF2F2;--ff-btn-hover-color:#B91C1C;--ff-btn-hover-border:#B91C1C;" +
1552                '">' +
1553                esc(t("withdraw")) +
1554                "</button>";
1555        }
1556
1557        html += "  </div>";
1558        html += "</div>";
1559
1560        modal.innerHTML = html;
1561        document.body.appendChild(modal);
1562        preferenceCenterEl = modal;
1563
1564        addEvent(modal, "click", function (e) {
1565            var btn = closest(e.target, "[data-action]");
1566            if (!btn) return;
1567            var action = btn.getAttribute("data-action");
1568            if (action === "close") closePreferenceCenter();
1569            else if (action === "save") savePreferences();
1570            else if (action === "pc-accept") {
1571                handleAcceptAll();
1572                closePreferenceCenter();
1573            } else if (action === "pc-reject") {
1574                handleRejectAll();
1575                closePreferenceCenter();
1576            } else if (action === "withdraw") {
1577                closePreferenceCenter();
1578                performWithdraw({ reload: false });
1579            }
1580        });
1581
1582        var overlay = modal.querySelector(".ff-cc-modal-overlay");
1583        if (overlay) addEvent(overlay, "click", closePreferenceCenter);
1584
1585        // v4.1.0: Dynamic toggle color update on change
1586        var toggleInputs = modal.querySelectorAll(
1587            '.ff-cc-toggle input[type="checkbox"]',
1588        );
1589        for (var ti = 0; ti < toggleInputs.length; ti++) {
1590            (function (input) {
1591                addEvent(input, "change", function () {
1592                    var slider = input.nextElementSibling;
1593                    if (slider) {
1594                        if (input.checked) {
1595                            slider.style.setProperty(
1596                                "--ff-toggle-active",
1597                                bannerConfig.primary_color || "#2563EB",
1598                            );
1599                        } else {
1600                            slider.style.removeProperty("--ff-toggle-active");
1601                        }
1602                    }
1603                });
1604            })(toggleInputs[ti]);
1605        }
1606
1607        trapFocus(modal);
1608        scheduleImpression("preference_center_opened");
1609        raf(function () {
1610            modal.classList.add("ff-cc-modal-visible");
1611        });
1612    }
1613
1614    function renderCategories() {
1615        if (
1616            !bannerConfig ||
1617            !bannerConfig.categories ||
1618            bannerConfig.categories.length === 0
1619        ) {
1620            return "<p>No categories configured.</p>";
1621        }
1622        return bannerConfig.categories
1623            .map(function (cat) {
1624                var isChecked = cat.is_essential || cat.is_enabled_by_default;
1625                if (currentConsent && currentConsent.categories) {
1626                    isChecked =
1627                        currentConsent.categories.indexOf(cat.id) !== -1;
1628                }
1629                if (cat.is_essential) isChecked = true;
1630
1631                var cookiesHtml = "";
1632                if (cat.cookies && cat.cookies.length > 0) {
1633                    cookiesHtml =
1634                        '<div class="ff-cc-cookies-list"><strong>' +
1635                        esc(t("cookies_used")) +
1636                        "</strong><ul>";
1637                    cat.cookies.forEach(function (ck) {
1638                        cookiesHtml +=
1639                            "<li><code>" +
1640                            esc(ck.name) +
1641                            "</code> &mdash; " +
1642                            esc(ck.purpose || ck.provider || "") +
1643                            (ck.duration ? " (" + esc(ck.duration) + ")" : "") +
1644                            "</li>";
1645                    });
1646                    cookiesHtml += "</ul></div>";
1647                }
1648
1649                return (
1650                    '<div class="ff-cc-category" style="border-color:' +
1651                    esc(bannerConfig.separator_color || "#E5E7EB") +
1652                    ';">' +
1653                    '<div class="ff-cc-category-header">' +
1654                    "<div><h4>" +
1655                    esc(cat.name) +
1656                    "</h4><p>" +
1657                    esc(cat.description) +
1658                    "</p></div>" +
1659                    '<div style="display:flex;align-items:center;gap:8px;flex-shrink:0;">' +
1660                    '<label class="ff-cc-toggle"><input type="checkbox" data-category-id="' +
1661                    cat.id +
1662                    '"' +
1663                    (isChecked ? " checked" : "") +
1664                    (cat.is_essential ? " disabled" : "") +
1665                    '><span class="ff-cc-toggle-slider" style="' +
1666                    (isChecked
1667                        ? "--ff-toggle-active:" +
1668                        esc(bannerConfig.primary_color) +
1669                        ";"
1670                        : "") +
1671                    '"></span></label>' +
1672                    (cat.is_essential
1673                        ? '<span style="font-size:11px;color:#6B7280;">' +
1674                        esc(t("always_active")) +
1675                        "</span>"
1676                        : "") +
1677                    "</div></div>" +
1678                    cookiesHtml +
1679                    "</div>"
1680                );
1681            })
1682            .join("");
1683    }
1684
1685    function closePreferenceCenter() {
1686        if (!preferenceCenterEl) return;
1687        preferenceCenterEl.classList.remove("ff-cc-modal-visible");
1688        var el = preferenceCenterEl;
1689        preferenceCenterEl = null;
1690        setTimeout(function () {
1691            if (el.parentNode) el.parentNode.removeChild(el);
1692        }, 350);
1693    }
1694
1695    function removePreferenceCenter() {
1696        if (preferenceCenterEl && preferenceCenterEl.parentNode)
1697            preferenceCenterEl.parentNode.removeChild(preferenceCenterEl);
1698        preferenceCenterEl = null;
1699    }
1700
1701    function savePreferences() {
1702        if (!preferenceCenterEl) return;
1703        var checkboxes = preferenceCenterEl.querySelectorAll(
1704            'input[type="checkbox"][data-category-id]',
1705        );
1706        var accepted = [],
1707            rejected = [];
1708        for (var i = 0; i < checkboxes.length; i++) {
1709            var catId = parseInt(
1710                checkboxes[i].getAttribute("data-category-id"),
1711                10,
1712            );
1713            if (checkboxes[i].checked) accepted.push(catId);
1714            else rejected.push(catId);
1715        }
1716        recordConsent("custom", accepted, rejected);
1717        closePreferenceCenter();
1718        hideConsentUI();
1719        scheduleRevisitTab();
1720    }
1721
1722    // ──────────────────────────────────────────────
1723    // Consent Actions
1724    // ──────────────────────────────────────────────
1725
1726    function handleAcceptAll() {
1727        var allIds = (bannerConfig.categories || []).map(function (c) {
1728            return c.id;
1729        });
1730        recordConsent("accept_all", allIds, []);
1731        hideConsentUI();
1732        unblockIframes();
1733        scheduleRevisitTab();
1734    }
1735
1736    function handleRejectAll() {
1737        var essential = [],
1738            rejected = [];
1739        (bannerConfig.categories || []).forEach(function (c) {
1740            if (c.is_essential) essential.push(c.id);
1741            else rejected.push(c.id);
1742        });
1743        recordConsent("reject_all", essential, rejected);
1744        hideConsentUI();
1745        scheduleRevisitTab();
1746    }
1747
1748    function scheduleRevisitTab() {
1749        setTimeout(function () {
1750            if (bannerConfig && bannerConfig.show_floating_button !== false) {
1751                createFloatingButton();
1752            }
1753        }, 500);
1754    }
1755
1756    /**
1757     * Record consent locally AND to API.
1758     *
1759     * FIX v4.0.1: Captures consent_id from API response and re-saves
1760     * the cookie with it. This is required for withdraw() to work.
1761     *
1762     * v4.1.0: Added cookie cleanup for rejected categories,
1763     * callback system, and DNT/GPC metadata.
1764     */
1765    function recordConsent(consentType, acceptedIds, rejectedIds) {
1766        var consent = {
1767            action: consentType,
1768            categories: acceptedIds,
1769            categories_rejected: rejectedIds,
1770            timestamp: Date.now(),
1771            expires:
1772                Date.now() +
1773                (bannerConfig ? bannerConfig.consent_duration_days : 365) *
1774                86400000,
1775            consent_id: null, // Will be set from API response
1776            dnt: dntEnabled, // v4.1.0
1777            gpc: gpcEnabled, // v4.1.0
1778        };
1779
1780        // Save immediately so consent takes effect even if API fails
1781        saveConsent(consent);
1782        currentConsent = consent;
1783        applyConsent(consent);
1784
1785        // v4.1.0: Clean up cookies from rejected categories
1786        if (
1787            bannerConfig &&
1788            bannerConfig.auto_block_cookies &&
1789            rejectedIds.length > 0
1790        ) {
1791            cleanupRejectedCookies(rejectedIds);
1792        }
1793
1794        if (bannerConfig && bannerConfig.enable_google_consent_mode)
1795            updateGoogleConsentMode(consent);
1796        if (bannerConfig && bannerConfig.enable_iab_tcf) updateIABTCF(consent);
1797        bindCustomRevisitButton();
1798
1799        // v4.1.0: Fire consent change callbacks
1800        fireConsentCallbacks(consent);
1801
1802        // Send to API and capture consent_id for future withdrawal
1803        sendConsentToAPI(consent)
1804            .then(function (responseData) {
1805                if (responseData && responseData.consent_id) {
1806                    consent.consent_id = responseData.consent_id;
1807                    saveConsent(consent); // Re-save with consent_id
1808                    currentConsent = consent;
1809                }
1810            })
1811            .catch(function (err) {
1812                console.warn("[FF-Consent] API consent tracking failed:", err);
1813            });
1814    }
1815
1816    /**
1817     * v4.1.0: Clean up cookies from rejected categories.
1818     * Reads cookie names from category config and deletes them.
1819     */
1820    function cleanupRejectedCookies(rejectedIds) {
1821        if (!bannerConfig || !bannerConfig.categories) return;
1822        var cookiesToDelete = [];
1823
1824        bannerConfig.categories.forEach(function (cat) {
1825            if (rejectedIds.indexOf(cat.id) !== -1 && cat.cookies) {
1826                cat.cookies.forEach(function (ck) {
1827                    if (ck.name) cookiesToDelete.push(ck.name);
1828                    // Support wildcard patterns like "_ga*"
1829                    if (ck.pattern) cookiesToDelete.push(ck.pattern);
1830                });
1831            }
1832        });
1833
1834        if (cookiesToDelete.length === 0) return;
1835
1836        // Get all current cookies
1837        var allCookies = document.cookie.split(";");
1838        allCookies.forEach(function (cookieStr) {
1839            var cookieName = cookieStr.split("=")[0].trim();
1840            var shouldDelete = cookiesToDelete.some(function (target) {
1841                if (target.indexOf("*") !== -1) {
1842                    // Wildcard match: "_ga*" matches "_ga", "_gat", "_gid"
1843                    var prefix = target.replace("*", "");
1844                    return cookieName.indexOf(prefix) === 0;
1845                }
1846                return cookieName === target;
1847            });
1848
1849            if (shouldDelete) {
1850                // Delete cookie with multiple path/domain combos
1851                var paths = ["/", window.location.pathname];
1852                var domains = [
1853                    "",
1854                    window.location.hostname,
1855                    "." + window.location.hostname,
1856                ];
1857                paths.forEach(function (path) {
1858                    domains.forEach(function (domain) {
1859                        var del =
1860                            cookieName +
1861                            "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
1862                            path;
1863                        if (domain) del += "; domain=" + domain;
1864                        document.cookie = del;
1865                    });
1866                });
1867                console.log("[FF-Consent] Deleted cookie:", cookieName);
1868            }
1869        });
1870    }
1871
1872    /**
1873     * v4.1.0: Fire all registered consent change callbacks
1874     */
1875    function fireConsentCallbacks(consent) {
1876        for (var i = 0; i < consentCallbacks.length; i++) {
1877            try {
1878                consentCallbacks[i](consent);
1879            } catch (err) {
1880                console.error("[FF-Consent] Callback error:", err);
1881            }
1882        }
1883    }
1884
1885    function sendConsentToAPI(consent) {
1886        var visitorCountry = getCookieValue("ff_country") || "IN";
1887        var visitorIp = getCookieValue("ff_visitor_ip");
1888
1889        return httpPost(API_URL + "/consent", {
1890            widget_id: CONFIG_ID,
1891            visitor_id: visitorId,
1892            consent_type: consent.action,
1893            categories_accepted: consent.categories || [],
1894            categories_rejected: consent.categories_rejected || [],
1895            page_url: window.location.href,
1896            language: currentLang,
1897            dnt: dntEnabled, // v4.1.0
1898            gpc: gpcEnabled, // v4.1.0
1899            user_agent: navigator.userAgent, // v4.1.0
1900            country_code: visitorCountry,
1901            vistor_ip: visitorIp,
1902        });
1903    }
1904
1905    // ──────────────────────────────────────────────
1906    // Script Blocking / Unblocking
1907    // ──────────────────────────────────────────────
1908
1909    function applyConsent(consent) {
1910        var allowed = consent.categories || [];
1911        var scripts = document.querySelectorAll("script[data-cookie-category]");
1912        for (var i = 0; i < scripts.length; i++) {
1913            var script = scripts[i];
1914            var catSlug = script.getAttribute("data-cookie-category");
1915            var catId = resolveCategoryId(catSlug);
1916            if (
1917                catId !== null &&
1918                allowed.indexOf(catId) !== -1 &&
1919                script.type === "text/plain"
1920            ) {
1921                var newScript = document.createElement("script");
1922                if (script.src) newScript.src = script.src;
1923                else newScript.textContent = script.textContent;
1924                var attrs = script.attributes;
1925                for (var k = 0; k < attrs.length; k++) {
1926                    if (
1927                        attrs[k].name !== "type" &&
1928                        attrs[k].name !== "data-cookie-category"
1929                    )
1930                        newScript.setAttribute(attrs[k].name, attrs[k].value);
1931                }
1932                script.parentNode.replaceChild(newScript, script);
1933            }
1934        }
1935
1936        // v4.1.0: Category-specific iframe unblocking (not just accept_all)
1937        if (consent.action === "accept_all") {
1938            unblockIframes();
1939        } else {
1940            unblockIframesByCategory(allowed);
1941        }
1942
1943        try {
1944            var event;
1945            if (typeof CustomEvent === "function")
1946                event = new CustomEvent("cookieConsentUpdated", {
1947                    detail: consent,
1948                });
1949            else {
1950                event = document.createEvent("CustomEvent");
1951                event.initCustomEvent(
1952                    "cookieConsentUpdated",
1953                    true,
1954                    true,
1955                    consent,
1956                );
1957            }
1958            window.dispatchEvent(event);
1959        } catch (e) { }
1960    }
1961
1962    /**
1963     * v4.1.0: Resolve category slug to category ID
1964     */
1965    function resolveCategoryId(slug) {
1966        if (!slug || !bannerConfig || !bannerConfig.categories) return null;
1967        for (var j = 0; j < bannerConfig.categories.length; j++) {
1968            if (bannerConfig.categories[j].slug === slug) {
1969                return bannerConfig.categories[j].id;
1970            }
1971        }
1972        return null;
1973    }
1974
1975    /**
1976     * v4.1.0: MutationObserver to block dynamically injected scripts
1977     */
1978    function startScriptObserver() {
1979        if (scriptObserver) return; // Already running
1980        if (typeof MutationObserver === "undefined") return;
1981
1982        scriptObserver = new MutationObserver(function (mutations) {
1983            mutations.forEach(function (mutation) {
1984                mutation.addedNodes.forEach(function (node) {
1985                    if (node.nodeType !== 1) return; // Not an element
1986                    // Check if it's a script with data-cookie-category
1987                    if (
1988                        node.tagName === "SCRIPT" &&
1989                        node.getAttribute("data-cookie-category")
1990                    ) {
1991                        var slug = node.getAttribute("data-cookie-category");
1992                        var catId = resolveCategoryId(slug);
1993                        var allowed = currentConsent
1994                            ? currentConsent.categories || []
1995                            : [];
1996
1997                        // If category not accepted, block the script
1998                        if (catId !== null && allowed.indexOf(catId) === -1) {
1999                            // Prevent execution by removing src and content
2000                            if (node.src) {
2001                                node.removeAttribute("src");
2002                            }
2003                            node.type = "text/plain";
2004                            console.log(
2005                                "[FF-Consent] Blocked dynamic script for category:",
2006                                slug,
2007                            );
2008                        }
2009                    }
2010                    // Also check child scripts within injected containers
2011                    if (node.querySelectorAll) {
2012                        var childScripts = node.querySelectorAll(
2013                            "script[data-cookie-category]",
2014                        );
2015                        for (var cs = 0; cs < childScripts.length; cs++) {
2016                            var cSlug = childScripts[cs].getAttribute(
2017                                "data-cookie-category",
2018                            );
2019                            var cId = resolveCategoryId(cSlug);
2020                            var cAllowed = currentConsent
2021                                ? currentConsent.categories || []
2022                                : [];
2023                            if (cId !== null && cAllowed.indexOf(cId) === -1) {
2024                                childScripts[cs].type = "text/plain";
2025                                if (childScripts[cs].src)
2026                                    childScripts[cs].removeAttribute("src");
2027                            }
2028                        }
2029                    }
2030                });
2031            });
2032        });
2033
2034        scriptObserver.observe(document.documentElement, {
2035            childList: true,
2036            subtree: true,
2037        });
2038    }
2039
2040    function stopScriptObserver() {
2041        if (scriptObserver) {
2042            scriptObserver.disconnect();
2043            scriptObserver = null;
2044        }
2045    }
2046
2047    // ──────────────────────────────────────────────
2048    // Iframe / Embed Blocking
2049    // ──────────────────────────────────────────────
2050
2051    function blockIframes() {
2052        if (!bannerConfig || !bannerConfig.block_iframes) return;
2053        var iframes = document.querySelectorAll(
2054            "iframe[data-cookie-category], iframe[data-ff-consent]",
2055        );
2056        for (var i = 0; i < iframes.length; i++) {
2057            var iframe = iframes[i];
2058            if (iframe.getAttribute("data-ff-blocked")) continue;
2059            iframe.setAttribute(
2060                "data-ff-original-src",
2061                iframe.src || iframe.getAttribute("data-src") || "",
2062            );
2063            iframe.removeAttribute("src");
2064            iframe.setAttribute("data-ff-blocked", "true");
2065
2066            var placeholder = document.createElement("div");
2067            placeholder.className = "ff-cc-iframe-placeholder";
2068            placeholder.innerHTML =
2069                '<div class="ff-cc-iframe-blocked-msg">' +
2070                '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
2070' +
2071                "<p>" +
2072                esc(bannerConfig.blocked_iframe_message) +
2073                "</p>" +
2074                '<button class="ff-cc-btn-save" data-action="iframe-accept" style="background:' +
2075                esc(bannerConfig.primary_color) +
2076                ';font-size:12px;padding:6px 14px;">' +
2077                esc(bannerConfig.accept_button_text || t("accept")) +
2078                "</button>" +
2079                "</div>";
2080            iframe.parentNode.insertBefore(placeholder, iframe);
2081            iframe.style.display = "none";
2082
2083            addEvent(placeholder, "click", function (e) {
2084                var btn = closest(e.target, '[data-action="iframe-accept"]');
2085                if (btn) handleAcceptAll();
2086            });
2087        }
2088    }
2089
2090    function unblockIframes() {
2091        var iframes = document.querySelectorAll("iframe[data-ff-blocked]");
2092        for (var i = 0; i < iframes.length; i++) {
2093            var iframe = iframes[i];
2094            var src = iframe.getAttribute("data-ff-original-src");
2095            if (src) iframe.src = src;
2096            iframe.removeAttribute("data-ff-blocked");
2097            iframe.removeAttribute("data-ff-original-src");
2098            iframe.style.display = "";
2099            var prev = iframe.previousElementSibling;
2100            if (prev && prev.classList.contains("ff-cc-iframe-placeholder")) {
2101                prev.parentNode.removeChild(prev);
2102            }
2103        }
2104    }
2105
2106    /**
2107     * v4.1.0: Unblock iframes only for accepted categories
2108     */
2109    function unblockIframesByCategory(allowedIds) {
2110        if (!bannerConfig || !bannerConfig.block_iframes) return;
2111        var iframes = document.querySelectorAll("iframe[data-ff-blocked]");
2112        for (var i = 0; i < iframes.length; i++) {
2113            var iframe = iframes[i];
2114            var catSlug =
2115                iframe.getAttribute("data-cookie-category") ||
2116                iframe.getAttribute("data-ff-consent");
2117            var catId = resolveCategoryId(catSlug);
2118
2119            if (catId !== null && allowedIds.indexOf(catId) !== -1) {
2120                var src = iframe.getAttribute("data-ff-original-src");
2121                if (src) iframe.src = src;
2122                iframe.removeAttribute("data-ff-blocked");
2123                iframe.removeAttribute("data-ff-original-src");
2124                iframe.style.display = "";
2125                var prev = iframe.previousElementSibling;
2126                if (
2127                    prev &&
2128                    prev.classList.contains("ff-cc-iframe-placeholder")
2129                ) {
2130                    prev.parentNode.removeChild(prev);
2131                }
2132            }
2133        }
2134    }
2135
2136    // ──────────────────────────────────────────────
2137    // Google Consent Mode v2
2138    // ──────────────────────────────────────────────
2139
2140    function setGoogleConsentModeDefaults() {
2141        if (
2142            !bannerConfig ||
2143            !bannerConfig.enable_google_consent_mode ||
2144            typeof gtag !== "function"
2145        )
2146            return;
2147        gtag("consent", "default", {
2148            ad_storage: "denied",
2149            ad_user_data: "denied",
2150            ad_personalization: "denied",
2151            analytics_storage: "denied",
2152            functionality_storage: "denied",
2153            personalization_storage: "denied",
2154            security_storage: "granted",
2155            wait_for_update: 500,
2156        });
2157    }
2158
2159    function initGoogleConsentModeFromLocal(consent) {
2160        // v4.1.0 FIX: Check enable_google_consent_mode flag
2161        if (bannerConfig && !bannerConfig.enable_google_consent_mode) return;
2162        if (typeof gtag !== "function") return;
2163        gtag("consent", "default", buildGCMState(consent));
2164    }
2165
2166    function updateGoogleConsentMode(consent) {
2167        if (typeof gtag !== "function") return;
2168        gtag("consent", "update", buildGCMState(consent));
2169    }
2170
2171    function buildGCMState(consent) {
2172        var accepted = consent.categories || [];
2173        var state = {
2174            ad_storage: "denied",
2175            ad_user_data: "denied",
2176            ad_personalization: "denied",
2177            analytics_storage: "denied",
2178            functionality_storage: "denied",
2179            personalization_storage: "denied",
2180            security_storage: "granted",
2181        };
2182        if (consent.action === "accept_all") {
2183            for (var k in state) {
2184                if (state.hasOwnProperty(k)) state[k] = "granted";
2185            }
2186            return state;
2187        }
2188        if (!bannerConfig || !bannerConfig.categories) return state;
2189        var slugs = [];
2190        bannerConfig.categories.forEach(function (cat) {
2191            if (accepted.indexOf(cat.id) !== -1) slugs.push(cat.slug);
2192        });
2193        if (slugs.indexOf("analytics") !== -1)
2194            state.analytics_storage = "granted";
2195        if (
2196            slugs.indexOf("marketing") !== -1 ||
2197            slugs.indexOf("advertising") !== -1
2198        ) {
2199            state.ad_storage = "granted";
2200            state.ad_user_data = "granted";
2201            state.ad_personalization = "granted";
2202        }
2203        if (
2204            slugs.indexOf("functional") !== -1 ||
2205            slugs.indexOf("preferences") !== -1
2206        ) {
2207            state.functionality_storage = "granted";
2208            state.personalization_storage = "granted";
2209        }
2210        // v4.1.0: social_media category maps to ad_storage + personalization
2211        if (
2212            slugs.indexOf("social_media") !== -1 ||
2213            slugs.indexOf("social") !== -1
2214        ) {
2215            state.ad_storage = "granted";
2216            state.ad_personalization = "granted";
2217            state.personalization_storage = "granted";
2218        }
2219        state.security_storage = "granted";
2220        return state;
2221    }
2222
2223    function updateIABTCF() {
2224        if (typeof window.__tcfapi === "function")
2225            console.log("[FF-Consent] IAB TCF update triggered");
2226    }
2227
2228    // ──────────────────────────────────────────────
2229    // Cookie Helpers
2230    // ──────────────────────────────────────────────
2231
2232    function saveConsent(consent) {
2233        setCookie(
2234            CONSENT_COOKIE,
2235            JSON.stringify(consent),
2236            bannerConfig ? bannerConfig.consent_duration_days : 365,
2237        );
2238    }
2239    function loadConsent() {
2240        var r = getCookieValue(CONSENT_COOKIE);
2241        if (!r) return null;
2242        try {
2243            return JSON.parse(r);
2244        } catch (e) {
2245            return null;
2246        }
2247    }
2248    function isConsentExpired(c) {
2249        return c.expires && Date.now() > c.expires;
2250    }
2251
2252    function setCookie(name, value, days) {
2253        var cookie =
2254            encodeURIComponent(name) +
2255            "=" +
2256            encodeURIComponent(value) +
2257            "; expires=" +
2258            new Date(Date.now() + days * 86400000).toUTCString() +
2259            "; path=/; SameSite=Lax";
2260        if (window.location.protocol === "https:") cookie += "; Secure";
2261        document.cookie = cookie;
2262    }
2263
2264    function getCookieValue(name) {
2265        var enc = encodeURIComponent(name) + "=",
2266            parts = document.cookie.split(";");
2267        for (var i = 0; i < parts.length; i++) {
2268            var c = parts[i].trim();
2269            if (c.indexOf(enc) === 0)
2270                return decodeURIComponent(c.substring(enc.length));
2271        }
2272        return null;
2273    }
2274
2275    // ──────────────────────────────────────────────
2276    // Revisit Tab Button
2277    // ──────────────────────────────────────────────
2278
2279    function createFloatingButton() {
2280        var existing = document.getElementById("ff-cc-floating-btn");
2281        if (existing) existing.parentNode.removeChild(existing);
2282
2283        var c = bannerConfig || {};
2284        var pos =
2285            c.floating_button_position ||
2286            c.revisit_button_position ||
2287            "bottom-left";
2288        var hoverText = c.revisit_button_text || t("manage");
2289        var primaryColor = c.primary_color || "#2563EB";
2290        var customImage = c.floating_image_button || null;
2291
2292        var btn = document.createElement("button");
2293        btn.id = "ff-cc-floating-btn";
2294        btn.className = "ff-cc-revisit-tab ff-cc-revisit-" + pos;
2295        btn.setAttribute("aria-label", hoverText);
2296        btn.setAttribute("title", hoverText);
2297
2298        var iconHtml;
2299        if (customImage) {
2300            iconHtml =
2301                '<span class="ff-cc-revisit-icon">' +
2302                '<img src="' +
2303                esc(customImage) +
2304                '" alt="" ' +
2305                'style="width:20px;height:20px;object-fit:contain;display:block;" ' +
2306                "onerror=\"this.style.display='none';this.nextElementSibling&&(this.nextElementSibling.style.display='inline')\">" +
2307                '<svg style="display:none" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
2308                '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>' +
2309                '<path d="M9 12l2 2 4-4" stroke-width="2.5"/>' +
2310                "</svg></span>";
2311        } else {
2312            iconHtml =
2313                '<span class="ff-cc-revisit-icon">' +
2314                '<img src="https://assets.flexifunnels.com/images/flexiicon.png" alt="" ' +
2315                'style="width:20px;height:20px;object-fit:contain;display:block;" ' +
2316                "onerror=\"this.style.display='none';this.nextElementSibling&&(this.nextElementSibling.style.display='inline')\">" +
2317                '<svg style="display:none" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
2318                '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>' +
2319                '<path d="M9 12l2 2 4-4" stroke-width="2.5"/>' +
2320                "</svg></span>";
2321        }
2322
2323        btn.innerHTML =
2324            iconHtml +
2325            '<span class="ff-cc-revisit-label">' +
2326            esc(hoverText) +
2327            "</span>";
2328        btn.style.setProperty("--ff-revisit-color", primaryColor);
2329
2330        addEvent(btn, "click", function (e) {
2331            e.preventDefault();
2332            e.stopPropagation();
2333            showPreferenceCenter();
2334        });
2335
2336        document.body.appendChild(btn);
2337    }
2338
2339    function removeFloatingButton() {
2340        var btn = document.getElementById("ff-cc-floating-btn");
2341        if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
2342    }
2343
2344    // ──────────────────────────────────────────────
2345    // Withdraw Consent — v4.0.2
2346    // ──────────────────────────────────────────────
2347
2348    function performWithdraw(options) {
2349        options = options || {};
2350        var shouldReload = options.reload !== false;
2351
2352        if (withdrawInProgress) {
2353            console.warn("[FF-Consent] Withdraw already in progress");
2354            return Promise.resolve({ status: "already_in_progress" });
2355        }
2356
2357        var consent = loadConsent();
2358        if (!consent) {
2359            console.warn("[FF-Consent] No consent to withdraw");
2360            return Promise.resolve({ status: "no_consent" });
2361        }
2362
2363        withdrawInProgress = true;
2364
2365        var payload = {
2366            widget_id: CONFIG_ID,
2367            visitor_id: visitorId,
2368            consent_id: consent.consent_id || "",
2369            page_url: window.location.href,
2370        };
2371
2372        return httpPost(
2373            "https://app.flexifunnels.com/api/cookie-consent/consent/withdraw",
2374            payload,
2375        )
2376            .then(function (responseData) {
2377                cleanupAfterWithdraw();
2378
2379                if (shouldReload) {
2380                    window.location.reload();
2381                } else {
2382                    if (bannerConfig) {
2383                        blockIframes();
2384                        showConsentUI();
2385                        scheduleImpression("banner_shown");
2386                    }
2387                }
2388
2389                return responseData || { status: "withdrawn" };
2390            })
2391            .catch(function (err) {
2392                console.warn("[FF-Consent] Withdraw API failed:", err);
2393
2394                cleanupAfterWithdraw();
2395
2396                if (shouldReload) {
2397                    window.location.reload();
2398                } else if (bannerConfig) {
2399                    blockIframes();
2400                    showConsentUI();
2401                    scheduleImpression("banner_shown");
2402                }
2403
2404                return {
2405                    status: "withdrawn_locally",
2406                    error: err.message || "API error",
2407                };
2408            })
2409            .finally(function () {
2410                withdrawInProgress = false;
2411            });
2412    }
2413
2414    function cleanupAfterWithdraw() {
2415        setCookie(CONSENT_COOKIE, "", -1);
2416        currentConsent = null;
2417
2418        try {
2419            sessionStorage.removeItem(CONFIG_CACHE_KEY);
2420        } catch (e) { }
2421
2422        resetGoogleConsentMode();
2423        removeFloatingButton();
2424
2425        fireEvent("cookieConsentWithdrawn", {
2426            visitor_id: visitorId,
2427            widget_id: CONFIG_ID,
2428            timestamp: Date.now(),
2429        });
2430    }
2431
2432    function resetGoogleConsentMode() {
2433        if (typeof gtag !== "function") return;
2434        try {
2435            gtag("consent", "update", {
2436                ad_storage: "denied",
2437                ad_user_data: "denied",
2438                ad_personalization: "denied",
2439                analytics_storage: "denied",
2440                functionality_storage: "denied",
2441                personalization_storage: "denied",
2442            });
2443        } catch (e) { }
2444    }
2445
2446    function fireEvent(name, detail) {
2447        try {
2448            var event;
2449            if (typeof CustomEvent === "function") {
2450                event = new CustomEvent(name, { detail: detail });
2451            } else {
2452                event = document.createEvent("CustomEvent");
2453                event.initCustomEvent(name, true, true, detail);
2454            }
2455            window.dispatchEvent(event);
2456        } catch (e) { }
2457    }
2458
2459    function bindCustomRevisitButton() {
2460        if (!bannerConfig || !bannerConfig.revisit_button_selector) return;
2461        try {
2462            var els = document.querySelectorAll(
2463                bannerConfig.revisit_button_selector,
2464            );
2465            for (var i = 0; i < els.length; i++) {
2466                if (!els[i].getAttribute("data-ff-revisit-bound")) {
2467                    els[i].setAttribute("data-ff-revisit-bound", "1");
2468                    addEvent(els[i], "click", function (e) {
2469                        e.preventDefault();
2470                        showPreferenceCenter();
2471                    });
2472                }
2473            }
2474        } catch (e) { }
2475    }
2476
2477    // ──────────────────────────────────────────────
2478    // Impressions
2479    // ──────────────────────────────────────────────
2480
2481    function scheduleImpression(type) {
2482        if (typeof requestIdleCallback !== "undefined")
2483            requestIdleCallback(
2484                function () {
2485                    sendImpression(type);
2486                },
2487                { timeout: 2000 },
2488            );
2489        else
2490            setTimeout(function () {
2491                sendImpression(type);
2492            }, 200);
2493    }
2494
2495    function sendImpression(type) {
2496        return true;
2497        httpPost(API_URL + "/impression", {
2498            widget_id: CONFIG_ID,
2499            visitor_id: visitorId,
2500            type: type,
2501            page_url: window.location.href,
2502        }).catch(function () { });
2503    }
2504
2505    // ──────────────────────────────────────────────
2506    // HTTP — with timeout and graceful error handling
2507    // ──────────────────────────────────────────────
2508
2509    function httpGet(url) {
2510        return fetch(url, {
2511            method: "GET",
2512            headers: { Accept: "application/json" },
2513            credentials: "omit",
2514        }).then(function (r) {
2515            if (!r.ok) throw new Error("HTTP " + r.status);
2516            return r.json();
2517        });
2518    }
2519
2520    /**
2521     * FIX v4.0.1: Added AbortController timeout (10s) and
2522     * graceful error handling for non-JSON responses.
2523     */
2524    function httpPost(url, body) {
2525        return new Promise(function (resolve, reject) {
2526            var controller =
2527                typeof AbortController !== "undefined"
2528                    ? new AbortController()
2529                    : null;
2530            var timeoutId = null;
2531
2532            if (controller) {
2533                timeoutId = setTimeout(function () {
2534                    controller.abort();
2535                }, 10000);
2536            }
2537
2538            var options = {
2539                method: "POST",
2540                headers: {
2541                    "Content-Type": "application/json",
2542                    Accept: "application/json",
2543                },
2544                credentials: "omit",
2545                body: JSON.stringify(body),
2546            };
2547            if (controller) options.signal = controller.signal;
2548
2549            fetch(url, options)
2550                .then(function (r) {
2551                    if (timeoutId) clearTimeout(timeoutId);
2552                    if (!r.ok) {
2553                        return r
2554                            .json()
2555                            .catch(function () {
2556                                return {};
2557                            })
2558                            .then(function (data) {
2559                                var err = new Error("HTTP " + r.status);
2560                                err.data = data;
2561                                throw err;
2562                            });
2563                    }
2564                    return r.json();
2565                })
2566                .then(resolve)
2567                .catch(function (err) {
2568                    if (timeoutId) clearTimeout(timeoutId);
2569                    reject(err);
2570                });
2571        });
2572    }
2573
2574    // ──────────────────────────────────────────────
2575    // DOM Utilities
2576    // ──────────────────────────────────────────────
2577
2578    function esc(str) {
2579        if (!str) return "";
2580        var el = document.createElement("span");
2581        el.textContent = String(str);
2582        return el.innerHTML;
2583    }
2584    function addEvent(el, type, fn) {
2585        if (el.addEventListener) el.addEventListener(type, fn, false);
2586        else if (el.attachEvent) el.attachEvent("on" + type, fn);
2587    }
2588    function closest(el, sel) {
2589        if (el.closest) return el.closest(sel);
2590        while (el && el !== document) {
2591            if (el.matches && el.matches(sel)) return el;
2592            el = el.parentNode;
2593        }
2594        return null;
2595    }
2596    function raf(fn) {
2597        if (typeof requestAnimationFrame === "function")
2598            requestAnimationFrame(fn);
2599        else setTimeout(fn, 16);
2600    }
2601
2602    function trapFocus(el) {
2603        var f = el.querySelectorAll(
2604            'button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])',
2605        );
2606        if (!f.length) return;
2607        var first = f[0],
2608            last = f[f.length - 1];
2609        first.focus();
2610        addEvent(el, "keydown", function (e) {
2611            if (e.key === "Escape" || e.keyCode === 27) {
2612                closePreferenceCenter();
2613                return;
2614            }
2615            if (e.key !== "Tab" && e.keyCode !== 9) return;
2616            if (e.shiftKey) {
2617                if (document.activeElement === first) {
2618                    e.preventDefault();
2619                    last.focus();
2620                }
2621            } else {
2622                if (document.activeElement === last) {
2623                    e.preventDefault();
2624                    first.focus();
2625                }
2626            }
2627        });
2628    }
2629
2630    // ──────────────────────────────────────────────
2631    // Embedded Styles
2632    // ──────────────────────────────────────────────
2633
2634    function injectStyles() {
2635        if (document.getElementById("ff-cc-styles")) return;
2636        var style = document.createElement("style");
2637        style.id = "ff-cc-styles";
2638        style.textContent = getCSS();
2639        (document.head || document.getElementsByTagName("head")[0]).appendChild(
2640            style,
2641        );
2642    }
2643
2644    function getCSS() {
2645        return (
2646            "" +
2647            ".ff-cc-wrapper,.ff-cc-wrapper *,.ff-cc-wrapper *::before,.ff-cc-wrapper *::after,.ff-cc-modal,.ff-cc-modal *,.ff-cc-modal *::before,.ff-cc-modal *::after{box-sizing:border-box;margin:0;padding:0}" +
2648            '.ff-cc-wrapper{position:fixed;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;opacity:0;visibility:hidden;transition:opacity .35s ease,transform .35s ease,visibility 0s .35s}' +
2649            ".ff-cc-wrapper.ff-cc-visible{opacity:1;visibility:visible;transition:opacity .35s ease,transform .35s ease,visibility 0s 0s}" +
2650            ".ff-cc-container{background:#fff;color:#1F2937;padding:20px 24px;overflow:hidden}" +
2651            ".ff-cc-bar{left:0;right:0;transform:translateY(100%)}.ff-cc-bar.ff-cc-pos-bottom,.ff-cc-bar.ff-cc-bottom{bottom:0}.ff-cc-bar.ff-cc-pos-top,.ff-cc-bar.ff-cc-top{top:0;transform:translateY(-100%)}.ff-cc-bar.ff-cc-visible{transform:translateY(0)}.ff-cc-bar .ff-cc-container{display:flex;align-items:center;gap:20px;padding:14px 24px;border-radius:0;box-shadow:0 -2px 10px rgba(0,0,0,.08)}.ff-cc-bar .ff-cc-content{flex:1;margin-bottom:0}.ff-cc-bar .ff-cc-title{font-size:15px;margin-bottom:2px}.ff-cc-bar .ff-cc-description{font-size:13px;margin-bottom:0}.ff-cc-bar .ff-cc-actions{flex-shrink:0;margin-bottom:0;gap:8px}.ff-cc-bar .ff-cc-btn{min-width:100px;flex:0;padding:8px 16px;font-size:13px}.ff-cc-bar .ff-cc-links{margin-top:4px}.ff-cc-bar .ff-cc-powered-by{display:none}" +
2652            ".ff-cc-box{max-width:420px;width:calc(100% - 32px);transform:translateY(20px)}" +
2653            ".ff-cc-box.ff-cc-pos-bottom,.ff-cc-box.ff-cc-pos-bottom-left,.ff-cc-box.ff-cc-pos-bottom-center,.ff-cc-box.ff-cc-pos-bottom-right,.ff-cc-box.ff-cc-bottom{bottom:16px}" +
2654            ".ff-cc-box.ff-cc-pos-top,.ff-cc-box.ff-cc-pos-top-left,.ff-cc-box.ff-cc-pos-top-center,.ff-cc-box.ff-cc-pos-top-right,.ff-cc-box.ff-cc-top{top:16px;transform:translateY(-20px)}" +
2655            ".ff-cc-box.ff-cc-pos-middle-left,.ff-cc-box.ff-cc-pos-middle-center,.ff-cc-box.ff-cc-pos-middle-right{top:50%;transform:translateY(-50%) translateY(20px)}" +
2656            ".ff-cc-box.ff-cc-pos-middle-left.ff-cc-visible,.ff-cc-box.ff-cc-pos-middle-center.ff-cc-visible,.ff-cc-box.ff-cc-pos-middle-right.ff-cc-visible{transform:translateY(-50%)}" +
2657            ".ff-cc-box.ff-cc-pos-top-left,.ff-cc-box.ff-cc-pos-middle-left,.ff-cc-box.ff-cc-pos-bottom-left,.ff-cc-box.ff-cc-left{left:16px}" +
2658            ".ff-cc-box.ff-cc-pos-top-right,.ff-cc-box.ff-cc-pos-middle-right,.ff-cc-box.ff-cc-pos-bottom-right,.ff-cc-box.ff-cc-right{right:16px}" +
2659            ".ff-cc-box.ff-cc-pos-top-center,.ff-cc-box.ff-cc-pos-middle-center,.ff-cc-box.ff-cc-pos-bottom-center,.ff-cc-box.ff-cc-center{left:50%;transform:translateX(-50%) translateY(20px)}" +
2660            ".ff-cc-box.ff-cc-pos-top-center.ff-cc-visible,.ff-cc-box.ff-cc-pos-bottom-center.ff-cc-visible,.ff-cc-box.ff-cc-center.ff-cc-visible{transform:translateX(-50%) translateY(0)}" +
2661            ".ff-cc-box:not(.ff-cc-pos-top):not(.ff-cc-pos-top-left):not(.ff-cc-pos-top-center):not(.ff-cc-pos-top-right):not(.ff-cc-pos-bottom):not(.ff-cc-pos-bottom-left):not(.ff-cc-pos-bottom-center):not(.ff-cc-pos-bottom-right):not(.ff-cc-pos-middle-left):not(.ff-cc-pos-middle-center):not(.ff-cc-pos-middle-right):not(.ff-cc-top):not(.ff-cc-bottom){bottom:16px}" +
2662            ".ff-cc-box:not(.ff-cc-pos-top-left):not(.ff-cc-pos-middle-left):not(.ff-cc-pos-bottom-left):not(.ff-cc-pos-top-right):not(.ff-cc-pos-middle-right):not(.ff-cc-pos-bottom-right):not(.ff-cc-pos-top-center):not(.ff-cc-pos-middle-center):not(.ff-cc-pos-bottom-center):not(.ff-cc-left):not(.ff-cc-right):not(.ff-cc-center){left:16px}" +
2663            ".ff-cc-box.ff-cc-visible{transform:translateY(0)}.ff-cc-box .ff-cc-container{border-radius:12px;padding:20px;box-shadow:0 8px 30px rgba(0,0,0,.12),0 2px 8px rgba(0,0,0,.06)}.ff-cc-box .ff-cc-title{font-size:16px}.ff-cc-box .ff-cc-description{font-size:13px}.ff-cc-box .ff-cc-actions{flex-direction:column}.ff-cc-box .ff-cc-btn{width:100%;flex:none}" +
2664            ".ff-cc-banner{left:0;right:0;transform:translateY(100%)}.ff-cc-banner.ff-cc-pos-bottom,.ff-cc-banner.ff-cc-bottom{bottom:0}.ff-cc-banner.ff-cc-pos-top,.ff-cc-banner.ff-cc-top{top:0;transform:translateY(-100%)}.ff-cc-banner.ff-cc-visible{transform:translateY(0)}.ff-cc-banner .ff-cc-container{border-radius:0;padding:20px 32px;box-shadow:0 -4px 16px rgba(0,0,0,.08)}.ff-cc-banner .ff-cc-content{max-width:900px}.ff-cc-banner .ff-cc-title{font-size:17px}.ff-cc-banner .ff-cc-description{font-size:13px}.ff-cc-banner .ff-cc-actions{max-width:900px}" +
2665            '.ff-cc-popup{top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;padding:20px;transform:none}.ff-cc-popup::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);opacity:0;transition:opacity .35s ease}.ff-cc-popup.ff-cc-visible::before{opacity:1}.ff-cc-popup .ff-cc-container{position:relative;z-index:1;max-width:520px;width:100%;border-radius:16px;padding:28px 28px 20px;box-shadow:0 25px 60px rgba(0,0,0,.2),0 8px 20px rgba(0,0,0,.1);transform:scale(.92) translateY(12px);transition:transform .35s ease}.ff-cc-popup.ff-cc-visible .ff-cc-container{transform:scale(1) translateY(0)}.ff-cc-popup .ff-cc-title{font-size:18px;text-align:center}.ff-cc-popup .ff-cc-description{text-align:center;font-size:13px}.ff-cc-popup .ff-cc-links{text-align:center}.ff-cc-popup .ff-cc-actions{justify-content:center}' +
2666            ".ff-cc-cloud{top:0;left:0;right:0;bottom:0;display:flex;padding:20px;transform:none}" +
2667            ".ff-cc-cloud.ff-cc-pos-top-left,.ff-cc-cloud.ff-cc-pos-top,.ff-cc-cloud.ff-cc-top{align-items:flex-start;justify-content:flex-start}" +
2668            ".ff-cc-cloud.ff-cc-pos-top-center{align-items:flex-start;justify-content:center}" +
2669            ".ff-cc-cloud.ff-cc-pos-top-right{align-items:flex-start;justify-content:flex-end}" +
2670            ".ff-cc-cloud.ff-cc-pos-middle-left{align-items:center;justify-content:flex-start}" +
2671            ".ff-cc-cloud.ff-cc-pos-middle-center,.ff-cc-cloud.ff-cc-center{align-items:center;justify-content:center}" +
2672            ".ff-cc-cloud.ff-cc-pos-middle-right{align-items:center;justify-content:flex-end}" +
2673            ".ff-cc-cloud.ff-cc-pos-bottom-left,.ff-cc-cloud.ff-cc-pos-bottom,.ff-cc-cloud.ff-cc-bottom,.ff-cc-cloud.ff-cc-left{align-items:flex-end;justify-content:flex-start}" +
2674            ".ff-cc-cloud.ff-cc-pos-bottom-center{align-items:flex-end;justify-content:center}" +
2675            ".ff-cc-cloud.ff-cc-pos-bottom-right,.ff-cc-cloud.ff-cc-right{align-items:flex-end;justify-content:flex-end}" +
2676            '.ff-cc-cloud::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.45);opacity:0;transition:opacity .35s ease}.ff-cc-cloud.ff-cc-visible::before{opacity:1}' +
2677            ".ff-cc-cloud .ff-cc-container{position:relative;z-index:1;max-width:520px;width:100%;border-radius:16px;padding:28px;box-shadow:0 25px 60px rgba(0,0,0,.2),0 8px 20px rgba(0,0,0,.1);transform:scale(.92);transition:transform .35s ease}.ff-cc-cloud.ff-cc-visible .ff-cc-container{transform:scale(1)}" +
2678            ".ff-cc-cloud .ff-cc-title{font-size:18px}.ff-cc-cloud .ff-cc-description{font-size:13px}" +
2679            ".ff-cc-floating{max-width:400px;width:calc(100% - 40px);transform:translateY(24px) scale(.96)}.ff-cc-floating.ff-cc-pos-bottom,.ff-cc-floating.ff-cc-pos-bottom-left,.ff-cc-floating.ff-cc-pos-bottom-center,.ff-cc-floating.ff-cc-pos-bottom-right,.ff-cc-floating.ff-cc-bottom{bottom:20px}.ff-cc-floating.ff-cc-pos-top,.ff-cc-floating.ff-cc-pos-top-left,.ff-cc-floating.ff-cc-pos-top-center,.ff-cc-floating.ff-cc-pos-top-right,.ff-cc-floating.ff-cc-top{top:20px;transform:translateY(-24px) scale(.96)}.ff-cc-floating.ff-cc-pos-top-left,.ff-cc-floating.ff-cc-pos-bottom-left,.ff-cc-floating.ff-cc-left{left:2
26790px}.ff-cc-floating.ff-cc-pos-top-right,.ff-cc-floating.ff-cc-pos-bottom-right,.ff-cc-floating.ff-cc-right{right:20px}.ff-cc-floating:not(.ff-cc-pos-top):not(.ff-cc-pos-top-left):not(.ff-cc-pos-top-center):not(.ff-cc-pos-top-right):not(.ff-cc-pos-bottom):not(.ff-cc-pos-bottom-left):not(.ff-cc-pos-bottom-center):not(.ff-cc-pos-bottom-right):not(.ff-cc-top):not(.ff-cc-bottom){bottom:20px}.ff-cc-floating:not(.ff-cc-pos-top-left):not(.ff-cc-pos-bottom-left):not(.ff-cc-pos-top-right):not(.ff-cc-pos-bottom-right):not(.ff-cc-left):not(.ff-cc-right){right:20px}.ff-cc-floating.ff-cc-visible{transform:translateY(0) scale(1)}.ff-cc-floating .ff-cc-container{border-radius:20px;padding:24px;box-shadow:0 12px 40px rgba(0,0,0,.15),0 4px 12px rgba(0,0,0,.08)}.ff-cc-floating .ff-cc-title{font-size:16px}.ff-cc-floating .ff-cc-description{font-size:13px}.ff-cc-floating .ff-cc-actions{flex-direction:column}.ff-cc-floating .ff-cc-btn{width:100%;flex:none;border-radius:10px}" +
2680            ".ff-cc-sidebar{top:0;bottom:0;width:380px;max-width:90vw;overflow-y:auto;-webkit-overflow-scrolling:touch}.ff-cc-sidebar.ff-cc-pos-left,.ff-cc-sidebar.ff-cc-left{left:0;transform:translateX(-100%)}.ff-cc-sidebar.ff-cc-pos-right,.ff-cc-sidebar.ff-cc-right{right:0;transform:translateX(100%)}.ff-cc-sidebar:not(.ff-cc-pos-left):not(.ff-cc-left):not(.ff-cc-pos-right):not(.ff-cc-right){right:0;transform:translateX(100%)}.ff-cc-sidebar.ff-cc-visible{transform:translateX(0)}.ff-cc-sidebar .ff-cc-container{border-radius:0;min-height:100%;padding:32px 24px;box-shadow:-4px 0 20px rgba(0,0,0,.1);display:flex;flex-direction:column}.ff-cc-sidebar.ff-cc-pos-left .ff-cc-container,.ff-cc-sidebar.ff-cc-left .ff-cc-container{box-shadow:4px 0 20px rgba(0,0,0,.1)}.ff-cc-sidebar .ff-cc-content{flex:1}.ff-cc-sidebar .ff-cc-title{font-size:20px;margin-bottom:12px}.ff-cc-sidebar .ff-cc-description{font-size:14px;line-height:1.7}.ff-cc-sidebar .ff-cc-actions{flex-direction:column;margin-top:auto;padding-top:20px}.ff-cc-sidebar .ff-cc-btn{width:100%;flex:none;padding:12px 20px}.ff-cc-sidebar-overlay{position:fixed;z-index:999998;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.35);opacity:0;visibility:hidden;transition:opacity .35s ease,visibility 0s .35s}.ff-cc-sidebar-overlay.ff-cc-visible{opacity:1;visibility:visible;transition:opacity .35s ease,visibility 0s 0s}" +
2681            ".ff-cc-buttons-flip .ff-cc-actions{flex-direction:column-reverse}" +
2682            ".ff-cc-bar.ff-cc-buttons-flip .ff-cc-actions{flex-direction:row-reverse}" +
2683            ".ff-cc-consent-wall{position:fixed;z-index:999998;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);opacity:0;visibility:hidden;transition:opacity .35s ease,visibility 0s .35s}.ff-cc-consent-wall.ff-cc-visible{opacity:1;visibility:visible;transition:opacity .35s ease,visibility 0s 0s}" +
2684            ".ff-cc-content{margin-bottom:16px}.ff-cc-title{font-size:17px;font-weight:600;margin:0 0 8px;color:#1F2937}.ff-cc-description{font-size:13px;color:#6B7280;margin:0 0 10px;line-height:1.6}.ff-cc-links{margin-top:8px}.ff-cc-links a{color:#2563EB;text-decoration:underline;margin-right:14px;font-size:12px;transition:color .2s}.ff-cc-links a:hover{color:#1D4ED8}.ff-cc-links a:focus-visible{outline:2px solid #2563EB;outline-offset:2px;border-radius:2px}" +
2685            ".ff-cc-actions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px}" +
2686            ".ff-cc-btn{padding:9px 18px;border-radius:8px;border:1px solid transparent;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;flex:1;min-width:110px;text-align:center;line-height:1.4;outline:none;font-family:inherit}" +
2687            ".ff-cc-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.1)}" +
2688            ".ff-cc-btn:hover{background:var(--ff-btn-hover-bg) !important;color:var(--ff-btn-hover-color) !important;border-color:var(--ff-btn-hover-border) !important}" +
2689            ".ff-cc-btn:active{transform:translateY(0);box-shadow:none}.ff-cc-btn:focus-visible{outline:2px solid #2563EB;outline-offset:2px}" +
2690            ".ff-cc-btn-save{padding:9px 22px;background:#2563EB;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s;font-family:inherit}.ff-cc-btn-save:hover{filter:brightness(0.9);box-shadow:0 4px 12px rgba(37,99,235,.3)}.ff-cc-btn-save:focus-visible{outline:2px solid #2563EB;outline-offset:2px}" +
2691            ".ff-cc-powered-by{text-align:center;font-size:10px;color:#9CA3AF;margin-top:10px}.ff-cc-powered-by strong{color:#2563EB}" +
2692            '.ff-cc-modal{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1000000;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility 0s .3s}.ff-cc-modal.ff-cc-modal-visible{opacity:1;visibility:visible;transition:opacity .3s ease,visibility 0s 0s}.ff-cc-modal-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5)}.ff-cc-modal-content{position:relative;background:#fff;border-radius:16px;max-width:680px;width:92%;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 25px 60px rgba(0,0,0,.18),0 8px 20px rgba(0,0,0,.08);transform:translateY(24px) scale(.96);transition:transform .3s ease}.ff-cc-modal-visible .ff-cc-modal-content{transform:translateY(0) scale(1)}.ff-cc-modal-header{padding:20px 24px 16px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}.ff-cc-modal-header h3{margin:0;font-size:18px;font-weight:600;color:#1F2937}.ff-cc-modal-close{background:none;border:none;font-size:26px;color:#9CA3AF;cursor:pointer;padding:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:8px;transition:all .2s;line-height:1}
2692.ff-cc-modal-close:hover{background:#F3F4F6;color:#1F2937}.ff-cc-modal-close:focus-visible{outline:2px solid #2563EB;outline-offset:2px}.ff-cc-modal-body{padding:20px 24px;overflow-y:auto;flex:1;-webkit-overflow-scrolling:touch}.ff-cc-modal-body>p{color:#6B7280;margin:0 0 16px;font-size:13px;line-height:1.6}.ff-cc-modal-footer{padding:16px 24px;border-top:1px solid #E5E7EB;display:flex;justify-content:flex-end;gap:8px;flex-shrink:0;flex-wrap:wrap}' +
2693            ".ff-cc-categories{display:flex;flex-direction:column;gap:12px}.ff-cc-category{border:1px solid #E5E7EB;border-radius:10px;padding:14px 16px;background:#F9FAFB;transition:border-color .2s}.ff-cc-category:hover{border-color:#D1D5DB}.ff-cc-category-header{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.ff-cc-category-header h4{margin:0 0 4px;font-size:14px;font-weight:600;color:#1F2937}.ff-cc-category-header p{margin:0;font-size:12px;color:#6B7280;line-height:1.5}" +
2694            '.ff-cc-toggle{position:relative;display:inline-block;width:44px;height:22px;flex-shrink:0}.ff-cc-toggle input{opacity:0;width:0;height:0;position:absolute}.ff-cc-toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#D1D5DB;transition:background .25s ease;border-radius:22px}.ff-cc-toggle-slider::before{content:"";position:absolute;height:16px;width:16px;left:3px;bottom:3px;background:#fff;transition:transform .25s ease;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.15)}.ff-cc-toggle input:checked+.ff-cc-toggle-slider{background:var(--ff-toggle-active,#2563EB)}.ff-cc-toggle input:checked+.ff-cc-toggle-slider::before{transform:translateX(22px)}.ff-cc-toggle input:disabled+.ff-cc-toggle-slider{opacity:.5;cursor:not-allowed}.ff-cc-toggle input:focus-visible+.ff-cc-toggle-slider{outline:2px solid #2563EB;outline-offset:2px}' +
2695            '.ff-cc-cookies-list{margin-top:10px;padding-top:10px;border-top:1px solid #E5E7EB;font-size:11px}.ff-cc-cookies-list strong{display:block;margin-bottom:6px;color:#374151}.ff-cc-cookies-list ul{margin:0;padding-left:18px;color:#6B7280;list-style-type:disc}.ff-cc-cookies-list li{margin-bottom:3px}.ff-cc-cookies-list code{background:#E5E7EB;padding:1px 5px;border-radius:3px;font-family:"Courier New",Courier,monospace;font-size:10px;color:#1F2937}' +
2696            ".ff-cc-iframe-placeholder{background:#F3F4F6;border:2px dashed #D1D5DB;border-radius:8px;padding:24px;display:flex;align-items:center;justify-content:center;min-height:120px}.ff-cc-iframe-blocked-msg{text-align:center;color:#6B7280}.ff-cc-iframe-blocked-msg svg{margin:0 auto 8px;display:block;color:#9CA3AF}.ff-cc-iframe-blocked-msg p{margin:0 0 12px;font-size:13px;line-height:1.5}" +
2697            "@keyframes ff-cc-slide-up{from{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}" +
2698            '.ff-cc-revisit-tab{position:fixed;z-index:999997;display:flex;align-items:center;gap:0;height:40px;border:none;border-radius:8px 8px 0 0;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:12px;font-weight:500;padding:0 12px 0 10px;overflow:hidden;max-width:42px;background:var(--ff-revisit-color,#2563EB);color:#fff;box-shadow:0 -2px 12px rgba(0,0,0,.18);white-space:nowrap;animation:ff-cc-slide-up .4s ease forwards;transition:max-width .3s ease,box-shadow .3s ease}' +
2699            ".ff-cc-revisit-tab:hover{max-width:320px;box-shadow:0 -4px 18px rgba(0,0,0,.25)}" +
2700            ".ff-cc-revisit-tab:focus-visible{outline:2px solid #fff;outline-offset:2px}" +
2701            ".ff-cc-revisit-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:18px;height:18px}" +
2702            ".ff-cc-revisit-label{margin-left:8px;opacity:0;transition:opacity .2s ease .1s;pointer-events:none}" +
2703            ".ff-cc-revisit-tab:hover .ff-cc-revisit-label{opacity:1}" +
2704            ".ff-cc-revisit-bottom-left{bottom:0;left:14px}" +
2705            ".ff-cc-revisit-bottom-right{bottom:0;right:14px}" +
2706            ".ff-cc-revisit-top-left{top:0;left:14px;border-radius:0 0 8px 8px}" +
2707            ".ff-cc-revisit-top-right{top:0;right:14px;border-radius:0 0 8px 8px}" +
2708            "@keyframes ff-cc-slide-down{from{transform:translateY(-100%);opacity:0}
2708to{transform:translateY(0);opacity:1}}" +
2709            ".ff-cc-revisit-top-left,.ff-cc-revisit-top-right{animation:ff-cc-slide-down .4s ease forwards}" +
2710            ".ff-cc-btn-withdraw{margin-top:4px;font-family:inherit}" +
2711            ".ff-cc-btn-withdraw:hover{background:var(--ff-btn-hover-bg) !important;color:var(--ff-btn-hover-color) !important;border-color:var(--ff-btn-hover-border) !important}" +
2712            ".ff-cc-theme-dark .ff-cc-container{background:#1F2937;color:#F9FAFB}.ff-cc-theme-dark .ff-cc-title{color:#F9FAFB}.ff-cc-theme-dark .ff-cc-description{color:#D1D5DB}.ff-cc-theme-dark .ff-cc-links a{color:#60A5FA}.ff-cc-theme-dark .ff-cc-powered-by{color:#6B7280}.ff-cc-theme-dark .ff-cc-powered-by strong{color:#60A5FA}.ff-cc-theme-dark .ff-cc-modal-content,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-content{background:#1F2937}.ff-cc-theme-dark .ff-cc-modal-header h3,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-header h3{color:#F9FAFB}.ff-cc-theme-dark .ff-cc-modal-header,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-header{border-bottom-color:#374151}.ff-cc-theme-dark .ff-cc-modal-footer,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-footer{border-top-color:#374151}.ff-cc-theme-dark .ff-cc-modal-body>p,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-body>p{color:#D1D5DB}.ff-cc-theme-dark .ff-cc-modal-close,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-close{color:#6B7280}.ff-cc-theme-dark .ff-cc-modal-close:hover,.ff-cc-modal.ff-cc-theme-dark .ff-cc-modal-close:hover{background:#374151;color:#F9FAFB}.ff-cc-theme-dark .ff-cc-category,.ff-cc-modal.ff-cc-theme-dark .ff-cc-category{background:#374151;border-color:#4B5563}.ff-cc-theme-dark .ff-cc-category:hover,.ff-cc-modal.ff-cc-theme-dark .ff-cc-category:hover{border-color:#6B7280}.ff-cc-theme-dark .ff-cc-category-header h4,.ff-cc-modal.ff-cc-theme-dark .ff-cc-category-header h4{color:#F9FAFB}.ff-cc-theme-dark .ff-cc-category-header p,.ff-cc-modal.ff-cc-theme-dark .ff-cc-category-header p{color:#9CA3AF}.ff-cc-theme-dark .ff-cc-cookies-list,.ff-cc-modal.ff-cc-theme-dark .ff-cc-cookies-list{border-top-color:#4B5563}.ff-cc-theme-dark .ff-cc-cookies-list strong,.ff-cc-modal.ff-cc-theme-dark .ff-cc-cookies-list strong{color:#D1D5DB}.ff-cc-theme-dark .ff-cc-cookies-list code,.ff-cc-modal.ff-cc-theme-dark .ff-cc-cookies-list code{background:#4B5563;color:#F9FAFB}.ff-cc-theme-dark .ff-cc-iframe-placeholder{background:#374151;border-color:#4B5563}.ff-cc-theme-dark .ff-cc-iframe-blocked-msg{color:#D1D5DB}" +
2713            ".ff-cc-theme-dark .ff-cc-btn-withdraw,.ff-cc-modal.ff-cc-theme-dark .ff-cc-btn-withdraw{color:#F87171;border-color:#F87171;--ff-btn-hover-bg:#450A0A;--ff-btn-hover-color:#FCA5A5;--ff-btn-hover-border:#FCA5A5}" +
2714            "@media(max-width:768px){.ff-cc-bar .ff-cc-container{flex-direction:column;align-items:stretch;padding:16px}.ff-cc-bar .ff-cc-actions{flex-direction:column}.ff-cc-bar.ff-cc-buttons-flip .ff-cc-actions{flex-direction:column-reverse}.ff-cc-bar .ff-cc-btn{width:100%;flex:none}.ff-cc-box{left:0!important;right:0!important;bottom:0!important;top:auto!important;max-width:100%;width:100%;transform:translateY(20px)!important}.ff-cc-box.ff-cc-visible{transform:translateY(0)!important}.ff-cc-box .ff-cc-container{border-radius:16px 16px 0 0}.ff-cc-banner .ff-cc-container{padding:16px}.ff-cc-banner .ff-cc-actions{flex-direction:column}.ff-cc-banner .ff-cc-btn{width:100%}.ff-cc-popup{padding:12px}.ff-cc-popup .ff-cc-container{max-width:100%;border-radius:16px}.ff-cc-cloud{padding:12px}.ff-cc-cloud .ff-cc-container{max-width:100%;border-radius:16px}.ff-cc-floating{left:0!important;right:0!important;bottom:0!important;top:auto!important;max-width:100%;width:100%}.ff-cc-floating .ff-cc-container{border-radius:20px 20px 0 0}.ff-cc-sidebar{width:100%;max-width:100vw}.ff-cc-modal-content{width:96%;max-height:90vh;border-radius:16px 16px 0 0;align-self:flex-end}.ff-cc-modal-footer{flex-direction:column}.ff-cc-modal-footer .ff-cc-btn,.ff-cc-modal-footer .ff-cc-btn-save,.ff-cc-modal-footer .ff-cc-btn-withdraw{width:100%;flex:none}.ff-cc-revisit-tab{height:36px;font-size:11px;padding:0 10px 0 8px;max-width:38px}.ff-cc-revisit-tab:hover{max-width:260px}.ff-cc-revisit-bottom-left{left:10px}.ff-cc-revisit-bottom-right{right:10px}}" +
2715            "@media print{.ff-cc-wrapper,.ff-cc-modal,.ff-cc-sidebar-overlay,.ff-cc-revisit-tab,.ff-cc-consent-wall{display:none!important}}" +
2716            "@media(prefers-reduced-motion:reduce){.ff-cc-wrapper,.ff-cc-wrapper *,.ff-cc-modal,.ff-cc-modal *,.ff-cc-modal-content,.ff-cc-btn,.ff-cc-btn-save,.ff-cc-btn-withdraw,.ff-cc-revisit-tab,.ff-cc-toggle-slider,.ff-cc-toggle-slider::before,.ff-cc-category,.ff-cc-popup::before,.ff-cc-cloud::before,.ff-cc-popup .ff-cc-container,.ff-cc-cloud .ff-cc-container,.ff-cc-sidebar-overlay,.ff-cc-consent-wall{transition:none!important;animation:none!important}}"
2717        );
2718    }
2719
2720    // ──────────────────────────────────────────────
2721    // Public API
2722    // ──────────────────────────────────────────────
2723
2724    window.CookieConsent = {
2725        show: function () {
2726            if (bannerConfig) showConsentUI();
2727            else
2728                fetchConfigurationCached().then(function (cfg) {
2729                    if (cfg) {
2730                        bannerConfig = cfg;
2731                        applyThemePreset();
2732                        showConsentUI();
2733                    }
2734                });
2735        },
2736        hide: hideConsentUI,
2737        showPreferences: showPreferenceCenter,
2738        getConsent: function () {
2739            return currentConsent;
2740        },
2741        getVisitorId: function () {
2742            return visitorId;
2743        },
2744        getLanguage: function () {
2745            return currentLang;
2746        },
2747        setLanguage: function (lang) {
2748            if (TRANSLATIONS[lang]) currentLang = lang;
2749        },
2750
2751        // Convenience methods (v4.0.2)
2752        acceptAll: function () {
2753            if (bannerConfig) handleAcceptAll();
2754        },
2755        rejectAll: function () {
2756            if (bannerConfig) handleRejectAll();
2757        },
2758
2759        /**
2760         * Withdraw consent — v4.0.2
2761         *
2762         * Options:
2763         *   reload: boolean (default true) — whether to reload after withdrawal
2764         *
2765         * Returns: Promise<{ status, consent_id?, withdrawal_id?, ... }>
2766         *
2767         * Flow:
2768         *  1. Calls API to record withdrawal server-side
2769         *  2. On success: clears cookie, resets GCM, removes floating btn
2770         *  3. Fires 'cookieConsentWithdrawn' event
2771         *  4. Either reloads page or re-shows banner
2772         */
2773        withdraw: function (options) {
2774            return performWithdraw(options);
2775        },
2776        reset: function () {
2777            setCookie(CONSENT_COOKIE, "", -1);
2778            currentConsent = null;
2779            try {
2780                sessionStorage.removeItem(CONFIG_CACHE_KEY);
2781            } catch (e) { }
2782            removeFloatingButton();
2783            resetGoogleConsentMode();
2784            stopScriptObserver(); // v4.1.0
2785            window.location.reload();
2786        },
2787        isConsentGiven: function () {
2788            return !!currentConsent && !isConsentExpired(currentConsent);
2789        },
2790        hasCategory: function (slug) {
2791            if (!currentConsent || !bannerConfig) return false;
2792            var cat = null;
2793            bannerConfig.categories.forEach(function (c) {
2794                if (c.slug === slug) cat = c;
2795            });
2796            return (
2797                cat &&
2798                currentConsent.categories &&
2799                currentConsent.categories.indexOf(cat.id) !== -1
2800            );
2801        },
2802        /**
2803         * v4.1.0: Register a callback for consent changes.
2804         * callback(consent) is called whenever consent is recorded.
2805         *
2806         * Usage:
2807         *   CookieConsent.on("change", function(consent) {
2808         *       if (consent.action === "accept_all") { ... }
2809         *   });
2810         */
2811        on: function (eventName, callback) {
2812            if (eventName === "change" && typeof callback === "function") {
2813                consentCallbacks.push(callback);
2814            }
2815        },
2816        /**
2817         * v4.1.0: Unregister a consent change callback.
2818         */
2819        off: function (eventName, callback) {
2820            if (eventName === "change") {
2821                consentCallbacks = consentCallbacks.filter(function (cb) {
2822                    return cb !== callback;
2823                });
2824            }
2825        },
2826        /**
2827         * v4.1.0: Get list of all accepted category slugs.
2828         * Returns: ["essential", "analytics", "marketing"] etc.
2829         */
2830        getAcceptedCategories: function () {
2831            if (!currentConsent || !bannerConfig) return [];
2832            var accepted = currentConsent.categories || [];
2833            return bannerConfig.categories
2834                .filter(function (c) {
2835                    return accepted.indexOf(c.id) !== -1;
2836                })
2837                .map(function (c) {
2838                    return c.slug;
2839                });
2840        },
2841        /**
2842         * v4.1.0: Get list of all rejected category slugs.
2843         */
2844        getRejectedCategories: function () {
2845            if (!currentConsent || !bannerConfig) return [];
2846            var rejected = currentConsent.categories_rejected || [];
2847            return bannerConfig.categories
2848                .filter(function (c) {
2849                    return rejected.indexOf(c.id) !== -1;
2850                })
2851                .map(function (c) {
2852                    return c.slug;
2853                });
2854        },
2855        /**
2856         * v4.1.0: Check if DNT or GPC is enabled
2857         */
2858        getPrivacySignals: function () {
2859            return { dnt: dntEnabled, gpc: gpcEnabled };
2860        },
2861        /**
2862         * v4.1.0: Get the full configuration object
2863         */
2864        getConfig: function () {
2865            return bannerConfig;
2866        },
2867        /**
2868         * v4.1.0: Programmatically accept specific categories by slug
2869         * Usage: CookieConsent.acceptCategories(["analytics", "functional"])
2870         */
2871        acceptCategories: function (slugs) {
2872            if (!bannerConfig) return;
2873            var accepted = [],
2874                rejected = [];
2875            bannerConfig.categories.forEach(function (c) {
2876                if (c.is_essential || slugs.indexOf(c.slug) !== -1) {
2877                    accepted.push(c.id);
2878                } else {
2879                    rejected.push(c.id);
2880                }
2881            });
2882            recordConsent("custom", accepted, rejected);
2883            hideConsentUI();
2884            scheduleRevisitTab();
2885        },
2886        /**
2887         * v4.1.0: Clear config cache and force re-fetch
2888         */
2889        clearCache: function () {
2890            try {
2891                sessionStorage.removeItem(CONFIG_CACHE_KEY);
2892            } catch (e) { }
2893        },
2894    };
2895
2896    // Auto-init
2897    if (document.readyState === "loading")
2898        document.addEventListener("DOMContentLoaded", init);
2899    else init();
2900})();

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.