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">×</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> — " + 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.