1// *** ADDED â Consent Mode v2 bootstrap *** 2// Must run BEFORE the privacy IIFE installs its stubs. 3// Uses the real gtag() function call (not raw array push) so that gtag.js 4// registers consent state via its consent API â this is what makes hits 5// report pscdl=granted/denied instead of pscdl=noapi. 6var DI_CONSENT_MODE_V2 = (typeof window.DiConsentMode === 'boolean') ? window.DiConsentMode : true; 7 8window.dataLayer = window.dataLayer || []; 9window.gtag = function () { window.dataLayer.push(arguments); }; 10 11window.google_tracking = window.google_tracking || {}; 12window.google_tracking.gtag = window.gtag; 13 14// Default: deny everything. Must be called before any Google tag loads. 15window.gtag('consent', 'default', { 16 analytics_storage: 'denied', 17 ad_storage: 'denied', 18 ad_user_data: 'denied', 19 ad_personalization: 'denied', 20 wait_for_update: 500 21}); 22 23// Returning visitor with stored consent: update immediately so GTM/GA 24// initialize already knowing consent is granted. 25(function () { 26 try { 27 var name = 'di_privacy_consent'; 28 var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)')); 29 if (match && decodeURIComponent(match[1]) === 'granted') { 30 window.gtag('consent', 'update', { 31 analytics_storage: 'granted', 32 ad_storage: 'granted', 33 ad_user_data: 'granted', 34 ad_personalization: 'granted' 35 }); 36 } 37 } catch (e) { } 38})(); 39// *** END ADDED â Consent Mode v2 bootstrap *** 40 41(function (window, document) { 42 'use strict'; 43 44 /* 45 Privacy / Consent Tracking Controller 46 47 Purpose: 48 - Pending consent: hold known/curated tracking scripts and API calls. 49 - Accepted consent: release held tracking scripts/calls immediately. 50 - Rejected consent: discard held tracking scripts/calls. 51 - GPC / Do Not Track: always treat as denied. 52 - Unknown third-party scripts are not held client-side; they are controlled by the server-side CSP policy. 53 54 Important: 55 - This script intentionally distinguishes between: 56 1) Non-essential trackers / analytics / pixels / session replay tools. 57 2) Functional third-party services that are required for the site experience. 58 3) Unknown third-party resources, which should be blocked by server-side CSP while consent is pending/denied. 59 */ 60 61 var CONSENT_KEY = 'di_privacy_consent'; 62 var COOKIE_MAX_AGE = 60 * 60 * 24 * 180; 63 64 var heldScripts = []; 65 var heldCalls = []; 66 var heldSockets = []; 67 68 function _logBlocked() { 69 console.log.apply(console, arguments); 70 } 71 72 function detectGPC() { 73 try { 74 // Global Privacy Control, where supported. 75 if (navigator && typeof navigator.globalPrivacyControl !== 'undefined') { 76 if (navigator.globalPrivacyControl) return true; 77 } 78 79 if (window.matchMedia && window.matchMedia('(global-privacy-control: 1)').matches) { 80 return true; 81 } 82 83 // Legacy browser "Do Not Track" support. 84 var dnt = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack; 85 if (dnt === '1' || dnt === 'yes') { 86 return true; 87 } 88 } catch (e) { } 89 90 return false; 91 } 92 93 function getStoredConsent() { 94 try { 95 var name = CONSENT_KEY.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 96 var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)')); 97 98 if (!match) { 99 return null; 100 } 101 102 var v = decodeURIComponent(match[1]); 103 104 if (v === 'granted' || v === 'denied') { 105 return v; 106 } 107 } catch (e) { } 108 109 return null; 110 } 111 112 function setStoredConsent(v) { 113 if (v !== 'granted' && v !== 'denied') { 114 return; 115 } 116 117 /* 118 The consent cookie is the single source of truth. 119 120 It controls: 121 - Server-side CSP mode 122 - JavaScript consent state 123 - Whether the banner appears 124 125 When this cookie expires, consent returns to "pending" and the banner 126 will be shown again. 127 */ 128 try { 129 document.cookie = 130 CONSENT_KEY + '=' + encodeURIComponent(v) + 131 '; Path=/' + 132 '; Max-Age=' + COOKIE_MAX_AGE + 133 '; SameSite=Lax' + 134 (window.location.protocol === 'https:' ? '; Secure' : ''); 135 } catch (e) { } 136 } 137 138 function clearStoredConsent() { 139 try { 140 document.cookie = 141 CONSENT_KEY + '=; Path=/; Max-Age=0; SameSite=Lax' + 142 (window.location.protocol === 'https:' ? '; Secure' : ''); 143 } catch (e) { } 144 } 145 146 function clearLegacyStoredConsent() { 147 try { 148 localStorage.removeItem(CONSENT_KEY); 149 } catch (e) { } 150 } 151 152 clearLegacyStoredConsent(); 153 154 var gpcEnabled = detectGPC(); 155 var storedConsent = getStoredConsent(); 156 var consent = storedConsent || null; 157 158 function getConsentState() { 159 if (gpcEnabled) return 'denied'; 160 return consent || 'pending'; 161 } 162 163 function trackingAllowed() { 164 return !gpcEnabled && consent === 'granted'; 165 } 166 167 function shouldHold() { 168 return !gpcEnabled && getConsentState() === 'pending'; 169 } 170 171 function shouldDrop() { 172 return gpcEnabled || getConsentState() === 'denied'; 173 } 174 175 /* 176 Early static script guard. 177 178 This catches hard-coded parser-discovered <script src="..."> tags that appear 179 later in the HTML, before the browser has a chance to execute them. 180
181 This is especially important for trackers added as normal static HTML tags, 182 such as Cloudflare Web Analytics / Browser Insights. 183 184 Note: 185 - This is not meant to replace CSP. 186 - CSP is still the hard server-side fallback. 187 - This guard exists so known static tracker scripts can be held/released 188 on the same page after consent. 189 */ 190 var earlyStaticScriptPatterns = [ 191 /static\.cloudflareinsights\.com\/beacon\.min\.js/i, 192 /js\.zi-scripts\.com\/zi-tag\.js/i 193 ]; 194 195 function earlyMatchesStaticScript(src) { 196 if (!src) return false; 197 198 var s = String(src); 199 200 for (var i = 0; i < earlyStaticScriptPatterns.length; i++) { 201 if (earlyStaticScriptPatterns[i].test(s)) { 202 return true; 203 } 204 } 205 206 return false; 207 } 208 209 function earlyCloneScriptForRelease(original) { 210 var clone = document.createElement('script'); 211 212 for (var i = 0; i < original.attributes.length; i++) { 213 var attr = original.attributes[i]; 214 215 try { 216 clone.setAttribute(attr.name, attr.value); 217 } catch (e) { } 218 } 219 220 return clone; 221 } 222 223 function earlyHoldStaticScript(script) { 224 if (!script || !script.src) return false; 225 226 var src = script.src; 227 228 if (!earlyMatchesStaticScript(src)) { 229 return false; 230 } 231 232 for (var i = 0; i < heldScripts.length; i++) { 233 if (heldScripts[i].src === src) { 234 return true; 235 } 236 } 237 238 if (shouldHold()) { 239 heldScripts.push({ 240 src: src, 241 node: earlyCloneScriptForRelease(script), 242 parent: document.head || document.documentElement, 243 method: 'appendChild', 244 refNode: null 245 }); 246 247 try { 248 if (script.parentNode) { 249 script.parentNode.removeChild(script); 250 } 251 } catch (e) { } 252 253 _logBlocked('[Privacy] Early-held static tracker script until consent â', src); 254 return true; 255 } 256 257 if (shouldDrop()) { 258 try { 259 if (script.parentNode) { 260 script.parentNode.removeChild(script); 261 } 262 } catch (e) { } 263 264 _logBlocked('[Privacy] Early-removed static tracker script â', src); 265 return true; 266 } 267 268 return false; 269 } 270 271 function installEarlyStaticScriptGuard() { 272 function scanExistingScriptsEarly() { 273 var scripts = document.querySelectorAll('script[src]'); 274 275 for (var i = 0; i < scripts.length; i++) { 276 earlyHoldStaticScript(scripts[i]); 277 } 278 } 279 280 scanExistingScriptsEarly(); 281 282 if (window.MutationObserver && document.documentElement) { 283 try { 284 var earlyObserver = new MutationObserver(function (mutations) { 285 for (var i = 0; i < mutations.length; i++) { 286 var added = mutations[i].addedNodes; 287 288 for (var j = 0; j < added.length; j++) { 289 var node = added[j]; 290 291 if (node && node.tagName === 'SCRIPT') { 292 earlyHoldStaticScript(node); 293 } 294 295 if (node && node.querySelectorAll) { 296 var scripts = node.querySelectorAll('script[src]'); 297 298 for (var k = 0; k < scripts.length; k++) { 299 earlyHoldStaticScript(scripts[k]); 300 } 301 } 302 } 303 } 304 }); 305 306 earlyObserver.observe(document.documentElement, { 307 childList: true, 308 subtree: true 309 }); 310 } catch (e) { } 311 } 312 } 313 314 installEarlyStaticScriptGuard(); 315 316 /* 317 Beacon/network endpoints. 318 319 These are non-essential analytics, ads, attribution, session replay, 320 heatmap, call tracking, fingerprinting, and marketing automation endpoints. 321 322 While consent is pending or denied, requests matching these patterns are 323 blocked client-side. This prevents known trackers from sending data even if 324 a script tries to fire a fetch/XHR/sendBeacon/image beacon. 325 */ 326 var beaconPatterns = [ 327 328 // Google Ads / AdSense / remarketing / conversion endpoints 329 /:\/\/ssl\.google-analytics\.com\//i, 330 /:\/\/stats\.g\.doubleclick\.net\//i, 331 /:\/\/www\.google\.com\/pagead\/1p-user-list/i, 332 /:\/\/www\.google\.com\/pagead\/conversion/i, 333 /:\/\/googleads\.g\.doubleclick\.net\//i, 334 /:\/\/pagead2\.googlesyndication\.com\//i, 335 /:\/\/www\.googlesyndication\.com\/pagead\//i, 336 /:\/\/www\.googleadservices\.com\/pagead\//i, 337 /:\/\/www\.google\.com\/pagead\//i, 338 339 // Meta / Facebook 340 /:\/\/www\.facebook\.com\/tr\?/i, 341 /:\/\/graph\.facebook\.com\/v\d+\.\d+\/events/i, 342 343 // TikTok 344 /:\/\/business\.tiktok\.com\/bt\//i, 345 /:\/\/s\.tiktok\.com\//i, 346 347 // Pinterest 348 /:\/\/log\.pinterest\.com\//i, 349 /:\/\/analytics\.pinterest\.com\//i, 350 351 // LinkedIn 352 /:\/\/px\.ads\.linkedin\.com/i, 353 354 // Snapchat 355 /:\/\/tr\.snapchat\.com\//i, 356 357 // Twitter / X 358 /:\/\/analytics\.twitter\.com\//i, 359 /:\/\/t\.co\/i\/collect/i, 360 361 // Reddit Ads 362 /:\/\/events\.redditmedia\.com\//i, 363 /:\/\/www\.redditstatic\.com\/ads\/pixel\.js/i, 364 /:\/\/alb\.reddit\.com\//i, 365 366 // Heatmaps / session replay / behavior tracking 367 /:\/\/static\.hotjar\.com\/c\/hotjar-/i, 368 /:\/\/track\.hotjar\.com\//i, 369 /:\/\/script\.crazyegg\.com\/pages\//i, 370 /:\/\/rs\.fullstory\.com/i, 371 /:\/\/api\.mouseflow\.com\//i, 372 /:\/\/www\.clarity\.ms\/collect/i, 373 /:\/\/tools\.luckyorange\.com\//i, 374 375 // Marketing automation / CRM / attribution 376 /:\/\/track\.hubspot\.com\//i, 377 /:\/\/api\.hsforms\.com\/submissions/i, 378 /:\/\/piwik\.pardot\.com\//i, 379 /:\/\/go\.pardot\.com\//i, 380 /:\/\/cdn\.segment\.com\/v1\/p\//i, 381 /:\/\/api\.mixpanel\.com\//i, 382 /:\/\/api\.amplitude\.com/i, 383 /:\/\/plausible\.io\/api\/event/i, 384 /:\/\/matomo\.cloud\/matomo\.php/i, 385 /:\/\/.*\/matomo\.php/i, 386 /:\/\/analytics\.liine\.com\//i, 387 /:\/\/instantleadcapture\.com\//i, 388 /:\/\/px\.premion\.com\//i, 389 /:\/\/js\.adsrvr\.org\//i, 390 /:\/\/cdn\.raek\.net\//i, 391 /:\/\/s1\.revenuewell\.com\//i, 392 /:\/\/diffuser-cdn\.app-us1\.com\//i, 393 /:\/\/chimpstatic\.com\//i, 394 395 // Fingerprinting-style endpoints or libraries. 396 /fingerprint/i, 397 /\.fpjs\.io/i, 398 399 // AdRoll 400 /:\/\/s\.adroll\.com\//i, 401 /:\/\/d\.adroll\.com\//i, 402 /:\/\/pixel\.adroll\.com\//i, 403 404 // ZoomInfo 405 /:\/\/ws\.zoominfo\.com\/pixel\//i, 406 /:\/\/ws\.zoominfo\.com\//i, 407 408 // VigLink / affiliate tracking 409 /:\/\/vigl\.us\//i, 410 411 // Trovo tag 412 /:\/\/tag\.trovo-tag\.com\//i, 413
414 // Programmatic adtech / cookie-sync / audience matching 415 /:\/\/x\.bidswitch\.net\/sync/i, 416 /:\/\/pixel\.tapad\.com\/idsync/i, 417 /:\/\/ml314\.com\/utsync\.ashx/i, 418 /:\/\/ps\.eyeota\.net\/match/i, 419 /:\/\/cm\.g\.doubleclick\.net\/pixel/i, 420 /:\/\/dsum-sec\.casalemedia\.com\/rum/i, 421 /:\/\/image2\.pubmatic\.com\/AdServer\/Pug/i, 422 /:\/\/pixel\.rubiconproject\.com\/tap\.php/i, 423 /:\/\/us-u\.openx\.net\/w\/1\.0\/sd/i, 424 /:\/\/sync\.outbrain\.com\/cookie-sync/i, 425 /:\/\/sync\.taboola\.com\//i, 426 /:\/\/eb2\.3lift\.com\/xuid/i, 427 /:\/\/ib\.adnxs\.com\/setuid/i, 428 429 // Patient Prism / call intelligence 430 /:\/\/next-api\.patientprism\.com\/v1\/snippet\//i, 431 432 // First-party legacy referral / page interaction tracking 433 // /:\/\/referrals\.di-api\.com\//i, 434 435 // Identity / ad targeting / profiling 436 /:\/\/pro\.ip-api\.com\/json\//i, 437 /:\/\/alocdn\.com\/.*\/xtarget\/p\.json/i, 438 /:\/\/tag\.link-url\.io\//i, 439 /:\/\/idx\.liadm\.com\//i, 440 441 // Cloudflare Web Analytics / Browser Insights 442 /:\/\/static\.cloudflareinsights\.com\/beacon/i, 443 /:\/\/cloudflareinsights\.com\//i, 444 445 // Additional tracker / analytics / adtech beacon endpoints 446 /:\/\/dni\.logmycalls\.com\//i, 447 /:\/\/insight\.adsrvr\.org\//i, 448 /:\/\/bat\.bing\.com\//i, 449 /:\/\/settings\.luckyorange\.com\//i, 450 /:\/\/app\.operadds\.com\//i, 451 /:\/\/js\.zi-scripts\.com\//i, 452 /:\/\/analytics-[0-9]+\.kxcdn\.com\//i, 453 /:\/\/alice\.liine\.com\//i, 454 /:\/\/data\.hockeystack\.com\/send/i,
455 /:\/\/jelly\.mdhv\.io\//i, 456 /:\/\/tag\.simpli\.fi\//i, 457 /:\/\/i\.simpli\.fi\/p/i, 458 /:\/\/um\.simpli\.fi\//i, 459 /:\/\/www\.redditstatic\.com\/ads\/pixel\.js/i, 460 /:\/\/dni\.logmycalls\.com\//i, 461 /:\/\/a\.usbrowserspeed\.com\/cs/i, 462 /:\/\/.*\.usbrowserspeed\.com\/cs/i, 463 464 // Pendo product analytics 465 /:\/\/cdn\.pendo\.io\//i, 466 /:\/\/data\.pendo\.io\//i, 467 /:\/\/app\.pendo\.io\//i, 468 469 // Piwik PRO analytics / tag manager 470 /:\/\/.*\.piwik\.pro\//i, 471 472 // Leadpages analytics 473 /:\/\/api\.leadpages\.io\/analytics\//i, 474 475 // BrandCDN / conversion pixel 476 /:\/\/adservices\.brandcdn\.com\/pixel\//i, 477 478 // LiveRamp / identity sync 479 /:\/\/idsync\.rlcdn\.com\//i, 480 481 // Kissmetrics analytics 482 /:\/\/i\.kissmetrics\.io\//i, 483 /:\/\/scripts\.kissmetrics\.io\//i, 484 /:\/\/trk\.kissmetrics\.io\//i, 485 /:\/\/et\.kissmetrics\.io\//i, 486 487 // RLETS / call tracking / attribution storage iframe 488 /:\/\/[^\/]+\.rlets\.com\/static\/storage\.html/i, 489 490 // ReachLocal / LocaliQ capture, attribution, geolocation enrichment 491 /:\/\/capture-api\.reachlocalservices\.com\//i, 492 493 // EXTENDED LIST --- 494 // Scheduling / booking widgets 495 /:\/\/[^\/]*calendly\.com\//i, 496 /:\/\/assets\.calendly\.com\//i, 497 /:\/\/[^\/]*localmed\.com\//i, 498 /:\/\/frontend\.assets\.nexhealth\.com\//i, 499 /:\/\/schedule\.jarvisanalytics\.com\//i, 500 /:\/\/embed\.acuityscheduling\.com\//i, 501 /:\/\/cdn\.timetrade\.com\//i, 502 /:\/\/book\.getweave\.com\//i, 503 /:\/\/book2\.getweave\.com\//i, 504 /:\/\/book\.gke1-west3\.wsf-prod-1\.wstack\.net\//i, 505 /:\/\/nextgen\.clixondental\.com\//i, 506 /:\/\/olb\.saloniris\.com\//i, 507 /:\/\/plugin\.mysalononline\.com\//i, 508 509 // Zocdoc 510 /:\/\/api\.zocdoc\.com\//i, 511 /:\/\/api2\.zocdoc\.com\//i, 512 /:\/\/bob\.zocdoc\.com\//i, 513 /:\/\/static\.zocdoc\.com\//i, 514 /:\/\/www\.zocdoc\.com\//i, 515 /:\/\/book\.zocdoc\.com\//i, 516 /:\/\/offsiteschedule\.zocdoc\.com\//i, 517 /:\/\/[^\/]+\.zocdoc\.com\//i, 518 519 // Chat / chatbot / AI assistant widgets 520 /:\/\/connect\.podium\.com\//i, 521 /:\/\/platform\.swellcx\.com\//i, 522 /:\/\/cdn\.apigateway\.co\//i, 523 /:\/\/chat\.gocloudgain\.com\//i, 524 /:\/\/chat\.dentina\.ai\//i, 525 /:\/\/static\.ai\.getdeardoc\.com\//i, 526 /:\/\/chat\.getdeardoc\.com\//i, 527 /:\/\/api\.chat\.getdeardoc\.com\//i, 528 /:\/\/app\.dasconsultantsusa\.com\//i, 529 /:\/\/server\.dasconsultantsusa\.com\//i, 530 /:\/\/app\.purechat\.com\//i, 531 /:\/\/widgetapi\.purechat\.com\//i, 532 /:\/\/[^\/]*botsrv\.com\//i, 533 /:\/\/botsrv2\.com\//i, 534 /:\/\/static\.botsrv2\.com\//i, 535 /:\/\/app\.botsify\.com\//i, 536 /:\/\/widget\.botsify\.com\//i, 537 /:\/\/embed\.tawk\.to\//i, 538 /:\/\/static-v\.tawk\.to\//i, 539 /:\/\/va\.tawk\.to\//i, 540 /:\/\/[^\/]+\.tawk\.to\//i, 541 /:\/\/[^\/]*formilla\.com\//i, 542 /:\/\/widgets\.leadconnectorhq\.com\//i, 543 /:\/\/services\.leadconnectorhq\.com\//i, 544 /:\/\/services\.msgsndr\.com\//i, 545 /:\/\/app\.chatsupport\.co\//i, 546 /:\/\/[^\/]*truelark\.com\//i, 547 /:\/\/ops\.truelark\.com\//i, 548 /:\/\/[^\/]*247aireceptionist\.com\//i, 549 /:\/\/code\.jivosite\.com\//i, 550 /:\/\/[^\/]+\.jivosite\.com\//i, 551 /:\/\/api\.livechatinc\.com\//i, 552 /:\/\/cdn\.livechatinc\.com\//i, 553 /:\/\/[^\/]+\.livechatinc\.com\//i, 554 /:\/\/chat\.doctorsinternet\.com\/web-bot\//i, 555 556 // Forms / lead capture / embedded form systems 557 /:\/\/webform\.adit\.com\//i, 558 /:\/\/embed\.lpcontent\.net\//i, 559 /:\/\/api\.leadpages\.io\//i, 560 /:\/\/app-widgets\.jotform\.io\//i, 561 /:\/\/widgets\.jotform\.io\//i, 562 /:\/\/forms\.liine\.com\//i, 563 /:\/\/dim\.dentalimplantmachine\.co\//i, 564 /:\/\/[^\/]+\.kit\.com\//i, 565 /:\/\/[^\/]+\.ck\.page\//i, 566 /:\/\/[^\/]*convertkit\.com\//i, 567 /:\/\/app\.convertkit\.com\//i, 568 /:\/\/f\.convertkit\.com\//i, 569 /:\/\/api\.hsforms\.com\//i, 570 /:\/\/forms\.hsforms\.com\//i, 571 /:\/\/js\.hsforms\.net\//i, 572 573 // Reviews / reputation / social proof widgets 574 /:\/\/go\.doctorsinternet\.com\//i, 575 /:\/\/go\.pimm-usa\.com\//i, 576 /:\/\/widgets-v7\.birdeye\.com\//i, 577 /:\/\/webchat\.birdeye\.com\//i, 578 /:\/\/widget-v7-cdn\.birdeye\.com\//i, 579 /:\/\/[^\/]*birdeye\.com\//i, 580 /:\/\/widgets\.revue\.us\//i, 581 /:\/\/widgets\.thereviewsplace\.com\//i, 582 /:\/\/assets\.thereviewsplace\.com\//i, 583 /:\/\/api\.thereviewsplace\.com\//i, 584 /:\/\/providers\.doctor\.com\//i, 585 /:\/\/app\.operadds\.com\//i, 586 /:\/\/reviewsonmywebsite\.com\//i, 587 /:\/\/localimpact\.com\//i, 588 /:\/\/local\.demandforce\.com\//i, 589 /:\/\/www\.demandforced3\.com\//i, 590 /:\/\/[^\/]*elfsight\.com\//i, 591 /:\/\/[^\/]*elfsightcdn\.com\//i, 592 593 // Social widgets / embeds 594 /:\/\/www\.instagram\.com\//i, 595 /:\/\/www\.tiktok\.com\//i, 596 /:\/\/lf16-tiktok-web\.tiktokcdn-us\.com\//i, 597 /:\/\/www\.facebook\.com\/plugins\//i, 598 /:\/\/widgets\.pinterest\.com\//i, 599 /:\/\/www\.yelp\.com\//i, 600 601 // Financing / payment / membership widgets 602 /:\/\/files\.withcherry\.com\//i, 603 /:\/\/gql\.withcherry\.com\//i, 604 /:\/\/master-cherry-storage\.s3\.us-west-2\.amazonaws\.com\//i, 605 /:\/\/member\.kleer\.com\//i, 606 /:\/\/member\.clerri\.com\//i, 607 /:\/\/checkout\.staxpayments\.com\//i, 608 /:\/\/goblueswipe\.transactiongateway\.com\//i, 609 /:\/\/secure\.networkmerchants\.com\//i, 610 /:\/\/apply\.sunbit\.com\//i, 611 /:\/\/www\.paypal\.com\//i, 612 /:\/\/www\.paypalobjects\.com\//i, 613 /:\/\/www\.carecredit\.com\//i, 614 615 // Patient / health / practice widgets that may create sessions or lead context 616 /:\/\/patient\.phreesia\.com\//i, 617 /:\/\/patient\.klara\.com\//i, 618 /:\/\/widget\.klara\.com\//i, 619 /:\/\/patient-api\.speareducation\.com\//i, 620 /:\/\/api\.modento\.io\//i, 621 /:\/\/app\.neuralityhealth\.ai\//i, 622 /:\/\/www\.smilevirtual\.com\//i, 623 /:\/\/[^\/]*doctible\.com\//i, 624 /:\/\/[^\/]*yapiapp\.io\//i, 625 626 // Intake / scheduling / patient forms 627 /:\/\/[^\/]*intakeq\.com\//i, 628 /:\/\/app\.prod\.intelibly\.com\//i, 629 /:\/\/app\.intelibly\.com\//i, 630 /:\/\/sc-widget\.prod\.intelibly\.com\//i, 631 /:\/\/clients-cdn\.pplpractice\.com\//i, 632 /:\/\/webrefillplugin\.bestrxconnect\.com\//i, 633 /:\/\/view\.implantsmiles\.co\//i, 634 635 // Simplifeye / patient communication 636 /:\/\/embed\.simplifeye\.co\//i, 637 /:\/\/api\.simplifeye\.co\//i, 638 639 // AI chat / chatbot / web assistant widgets 640 /:\/\/aichatbotweb\.revenuewell\.com\//i, 641 /:\/\/[^\/]+\.web-assistant\.tensorlinks\.app\//i, 642 /:\/\/[^\/]*tensorlinks\.app\//i, 643 /:\/\/api\.heygent\.ai\//i, 644 /:\/\/lexicon-prod\.apigateway\.co\//i, 645 /:\/\/conversation-prod\.apigateway\.co\//i, 646 /:\/\/widget\.osam\.one\//i, 647 648 // Local marketing / lead widgets 649 /:\/\/www\.localmarketingmanager\.com\//i, 650 /:\/\/[^\/]*localmarketingmanager\.com\//i, 651 652 // Health / patient commerce / referral widgets 653 /:\/\/assets\.fullscript\.com\//i, 654 /:\/\/www\.opencare\.com\//i, 655 /:\/\/[^\/]*opencare\.com\//i 656 // END EXTENDED LIST --- 657 ]; 658 659 if (!DI_CONSENT_MODE_V2) { 660 beaconPatterns.push( 661 /:\/\/www\.google-analytics\.com\/collect/i, 662 /:\/\/www\.google-analytics\.com\/g\/collect/i, 663 /:\/\/www\.googletagmanager\.com\/collect/i, 664 /:\/\/region1\.google-analytics\.com\/g\/collect/i, 665 /:\/\/region2\.google-analytics\.com\/g\/collect/i, 666 /:\/\/analytics\.google\.com\/g\/collect/i 667 ); 668 } 669 /* 670 Script loader patterns. 671 672 These are known non-essential tracker scripts. While consent is pending, 673 the script is held instead of loaded. If consent is accepted, held scripts 674 are released on the same page. If consent is denied, held scripts are discarded. 675 */ 676 var blockUrls = [ 677 678 // Google Ads / AdSense / remarketing scripts 679 /google\.com\/pagead\/tag\/js/i, 680 /googleads\.g\.doubleclick\.net\//i, 681 /pagead2\.googlesyndication\.com\//i, 682 /pagead2\.googlesyndication\.com\/pagead\//i, 683 /www\.googlesyndication\.com\/pagead\//i, 684 /www\.googleadservices\.com\/pagead\//i, 685 686 // Meta / Facebook 687 /connect\.facebook\.net\/.*\/fbevents\.js/i, 688 /facebook\.com\/tr\/?/i, 689 690 // TikTok 691 /business\.tiktok\.com\/widget/i, 692 /s\.tiktok\.com\//i, 693 /analytics\.tiktok\.com\/i18n\/pixel\/events\.js/i, 694 695 // LinkedIn 696 /snap\.licdn\.com\/li\.js/i, 697 /snap\.licdn\.com\/li\.lms-analytics\/insight\.min\.js/i, 698 699 // Twitter / X 700 /static\.ads-twitter\.com\/uwt\.js/i, 701 702 // Pinterest 703 /assets\.pinterest\.com\/js\/pinit\.js/i, 704 705 // Snapchat 706 /sc-static\.net\/connect\.js/i, 707 708 // Heatmaps / session replay / user behavior analytics 709 /static\.hotjar\.com\/c\/hotjar-/i, 710 /tools\.luckyorange\.com\/core\/lo\.js/i, 711 /fullstory\.com\/s\/fs\.js/i, 712 /clarity\.ms\/tag/i, 713 /script\.crazyegg\.com\/pages\//i, 714 /mouseflow\.com\/projects\//i, 715 716 // Analytics / CDP / product analytics 717 /cdn\.mixpanel\.com\/lib/i, 718 /cdn\.amplitude\.com\/js/i, 719 /cdn\.segment\.com\/analytics/i, 720 /plausible\.io\/js\/script/i, 721 722 // Call tracking / DNI / lead capture / attribution 723 /tctm\.co\/t\.js/i, 724 /appcontent\.practicenumbers\.com\/phone_number_replace_/i, 725 /cdn\.callrail\.com\/companies\/.+\/swap\.js/i, 726 /cdn\.calltrk\.com\/companies\/.+\/swap\.js/i, 727 /cdn\.rlets\.com\/capture_configs\//i, 728 /analytics\.liine\.com\/v1\/bootstrapped/i, 729 /instantleadcapture\.com\/px\.min\.js/i, 730 /js\.adsrvr\.org\/up_loader/i, 731 /px\.premion\.com\/pixel\.js/i, 732 733 // Anti-fraud / visitor ID / attribution-style scripts 734 /www\.clickcease\.com\/monitor\/.+\.js/i, 735 /cdn\.raek\.net\/js\/raek\.min\.js/i, 736 /hockeystack(@latest)?\/hockeystack\.min\.js/i, 737 738 // Chat/CX scripts that include explicit tracking behavior. 739 /cdn\.livechatinc\.com\/tracking\.js/i, 740 /static\.mobilemonkey\.com\/js\/business_/i, 741 /static\.zdassets\.com\/ekr\/snippet\.js/i, 742 /platform\.swellcx\.com\/widget\/js\/chat\.js/i, 743 /cdn\.apigateway\.co\/webchat-client/i, 744 745 // Marketing automation / email / CRM 746 /js\.hubspot\.com\/analytics/i, 747 /js\.hs-scripts\.com\/\d+\.js/i, 748 /js\.hsforms\.net\/forms\/embed\/v2\.js/i, 749 /chimpstatic\.com\/mcjs-connected\/js\//i, 750 /diffuser-cdn\.app-us1\.com\/diffuser\/diffuser\.js/i, 751 /tag\.brandcdn\.com\/autoscript\//i, 752 /tag\.validate\.audio\/validate\.js/i, 753 /s1\.revenuewell\.com\/scripts\/js-api\.js/i, 754 755 // BrandCDN conversion pixel script 756 /adservices\.brandcdn\.com\/pixel\/cv_pixel\.js/i, 757 758 // AdRoll 759 /s\.adroll\.com\/j\/[^\/]+\/roundtrip\.js/i, 760 761 // ZoomInfo pixel 762 /ws\.zoominfo\.com\/pixel\//i, 763 764 // Trovo tag iframe/script 765 /tag\.trovo-tag\.com\//i, 766 767 // Patient Prism / call intelligence 768 /next-api\.patientprism\.com\/v1\/snippet\//i, 769 770 // Cloudflare Web Analytics / Browser Insights 771 /static\.cloudflareinsights\.com\/beacon\.min\.js/i, 772 773 // Additional tracker scripts 774 /dni\.logmycalls\.com\/dni\.js/i, 775 /insight\.adsrvr\.org\//i, 776 /bat\.bing\.com\/bat\.js/i, 777 /settings\.luckyorange\.com\//i, 778 /app\.operadds\.com\//i, 779 /js\.zi-scripts\.com\/zi-tag\.js/i, 780 /analytics-[0-9]+\.kxcdn\.com\/analytics\.js/i, 781 /tag\.simpli\.fi\//i, 782 /i\.simpli\.fi\/p/i, 783 /www\.redditstatic\.com\/ads\/pixel\.js/i, 784 /dni\.logmycalls\.com\/dni\.js/i, 785 786 // Pendo product analytics 787 /cdn\.pendo\.io\/agent\/static\/[^\/]+\/pendo\.js/i, 788 789 // Piwik PRO container / analytics 790 /containers\.piwik\.pro\/[^\/]+\.js/i, 791 792 // Kissmetrics analytics scripts 793 /i\.kissmetrics\.io\/i\.js/i, 794 /scripts\.kissmetrics\.io\/.*\.js/i, 795 796 // EXTENDED LIST --- 797 // Scheduling / booking widgets 798 /calendly\.com/i, 799 /assets\.calendly\.com/i, 800 /localmed\.com/i, 801 /frontend\.assets\.nexhealth\.com/i, 802 /schedule\.jarvisanalytics\.com/i, 803 /embed\.acuityscheduling\.com/i, 804 /cdn\.timetrade\.com/i, 805 /book\.getweave\.com/i, 806 /book2\.getweave\.com/i, 807 /book\.gke1-west3\.wsf-prod-1\.wstack\.net/i, 808 /nextgen\.clixondental\.com/i, 809 /olb\.saloniris\.com/i, 810 /plugin\.mysalononline\.com/i, 811 812 // Zocdoc 813 /zocdoc\.com/i, 814 /zocdocstatic\.com/i, 815 /static\.zocdoc\.com/i, 816 /offsiteschedule\.zocdoc\.com/i, 817 /book\.zocdoc\.com/i, 818 819 // Chat / chatbot / AI assistant widgets 820 /connect\.podium\.com/i, 821 /platform\.swellcx\.com/i, 822 /cdn\.apigateway\.co/i, 823 /chat\.gocloudgain\.com/i, 824 /chat\.dentina\.ai/i, 825 /static\.ai\.getdeardoc\.com/i, 826 /chat\.getdeardoc\.com/i, 827 /api\.chat\.getdeardoc\.com/i, 828 /app\.dasconsultantsusa\.com/i, 829 /server\.dasconsultantsusa\.com/i, 830 /app\.purechat\.com/i, 831 /widgetapi\.purechat\.com/i, 832 /botsrv\.com/i, 833 /botsrv2\.com/i, 834 /static\.botsrv2\.com/i, 835 /app\.botsify\.com/i, 836 /widget\.botsify\.com/i, 837 /embed\.tawk\.to/i, 838 /static-v\.tawk\.to/i, 839 /va\.tawk\.to/i, 840 /formilla\.com/i, 841 /widgets\.leadconnectorhq\.com/i, 842 /services\.leadconnectorhq\.com/i, 843 /services\.msgsndr\.com/i, 844 /app\.chatsupport\.co/i, 845 /truelark\.com/i, 846 /ops\.truelark\.com/i, 847 /247aireceptionist\.com/i, 848 /code\.jivosite\.com/i, 849 /jivosite\.com/i, 850 /api\.livechatinc\.com/i, 851 /cdn\.livechatinc\.com/i, 852 /chat\.doctorsinternet\.com/i, 853 854 // Forms / lead capture / embedded form systems 855 /webform\.adit\.com/i, 856 /embed\.lpcontent\.net/i,
857 /api\.leadpages\.io/i, 858 /app-widgets\.jotform\.io/i, 859 /widgets\.jotform\.io/i, 860 /forms\.liine\.com/i, 861 /dim\.dentalimplantmachine\.co/i, 862 /kit\.com/i, 863 /ck\.page/i, 864 /convertkit\.com/i, 865 /app\.convertkit\.com/i, 866 /f\.convertkit\.com/i, 867 /api\.hsforms\.com/i, 868 /forms\.hsforms\.com/i, 869 /js\.hsforms\.net/i, 870 871 // Reviews / reputation / social proof widgets 872 /go\.doctorsinternet\.com/i, 873 /go\.pimm-usa\.com/i, 874 /widgets-v7\.birdeye\.com/i, 875 /webchat\.birdeye\.com/i, 876 /widget-v7-cdn\.birdeye\.com/i, 877 /birdeye\.com/i, 878 /widgets\.revue\.us/i, 879 /widgets\.thereviewsplace\.com/i, 880 /assets\.thereviewsplace\.com/i, 881 /api\.thereviewsplace\.com/i, 882 /providers\.doctor\.com/i, 883 /app\.operadds\.com/i, 884 /reviewsonmywebsite\.com/i, 885 /localimpact\.com/i, 886 /local\.demandforce\.com/i, 887 /demandforced3\.com/i, 888 /elfsight\.com/i, 889 /elfsightcdn\.com/i, 890 891 // Social widgets / embeds 892 /instagram\.com/i, 893 /tiktok\.com/i, 894 /lf16-tiktok-web\.tiktokcdn-us\.com/i, 895 /facebook\.com\/plugins/i, 896 /widgets\.pinterest\.com/i, 897 /www\.yelp\.com/i, 898 899 // Financing / payment / membership widgets 900 /files\.withcherry\.com/i, 901 /gql\.withcherry\.com/i, 902 /master-cherry-storage\.s3\.us-west-2\.amazonaws\.com/i, 903 /member\.kleer\.com/i, 904 /member\.clerri\.com/i, 905 /checkout\.staxpayments\.com/i, 906 /goblueswipe\.transactiongateway\.com/i, 907 /secure\.networkmerchants\.com/i, 908 /apply\.sunbit\.com/i, 909 /www\.paypal\.com/i, 910 /www\.paypalobjects\.com/i, 911 /www\.carecredit\.com/i, 912 913 // Patient / health / practice widgets that may create sessions or lead context 914 /patient\.phreesia\.com/i, 915 /patient\.klara\.com/i, 916 /widget\.klara\.com/i, 917 /patient-api\.speareducation\.com/i, 918 /api\.modento\.io/i, 919 /app\.neuralityhealth\.ai/i, 920 /www\.smilevirtual\.com/i, 921 /doctible\.com/i, 922 /yapiapp\.io/i, 923 924 // Intake / scheduling / patient forms 925 /intakeq\.com/i, 926 /app\.prod\.intelibly\.com/i, 927 /app\.intelibly\.com/i, 928 /sc-widget\.prod\.intelibly\.com/i, 929 /clients-cdn\.pplpractice\.com/i, 930 /webrefillplugin\.bestrxconnect\.com/i, 931 /view\.implantsmiles\.co/i, 932 933 // Simplifeye / patient communication 934 /embed\.simplifeye\.co/i, 935 /api\.simplifeye\.co/i, 936 937 // AI chat / chatbot / web assistant widgets 938 /aichatbotweb\.revenuewell\.com/i, 939 /web-assistant\.tensorlinks\.app/i, 940 /tensorlinks\.app/i, 941 /api\.heygent\.ai/i, 942 /lexicon-prod\.apigateway\.co/i, 943 /conversation-prod\.apigateway\.co/i, 944 /widget\.osam\.one/i, 945 946 // Local marketing / lead widgets 947 /www\.localmarketingmanager\.com/i, 948 /localmarketingmanager\.com/i, 949 950 // Health / patient commerce / referral widgets 951 /assets\.fullscript\.com/i, 952 /www\.opencare\.com/i, 953 /opencare\.com/i 954 // END EXTENDED LIST --- 955 ]; 956 957 958 if (!DI_CONSENT_MODE_V2) { 959 blockUrls.push( 960 /googletagmanager\.com\/gtag\/js/i, 961 /googletagmanager\.com\/gtm\.js/i 962 ); 963 } 964 965 /* 966 First-party analytics websocket. 967 */ 968 var socketPatterns = [ 969 // /^wss:\/\/socket\.di-api\.com\/analytics/i 970 ]; 971 972 function matchesPattern(url, patterns) { 973 if (!url) return false; 974 975 var s = String(url); 976 977 return patterns.some(function (r) { 978 return r.test(s); 979 }); 980 } 981 982 function shouldBlockUrl(url) { 983 return matchesPattern(url, beaconPatterns); 984 } 985 986 function shouldBlockScript(url) { 987 return matchesPattern(url, blockUrls); 988 } 989 990 function shouldHoldSocket(url) { 991 return matchesPattern(url, socketPatterns); 992 } 993 994 function holdScript(src, node, parent, method, refNode) { 995 if (!src) return; 996 997 for (var i = 0; i < heldScripts.length; i++) { 998 if (heldScripts[i].src === src) return; 999 } 1000 1001 heldScripts.push({ 1002 src: src, 1003 node: node || null, 1004 parent: parent || null, 1005 method: method || 'appendChild', 1006 refNode: refNode || null 1007 }); 1008 1009 _logBlocked('[Privacy] Held tracker script until consent â', src); 1010 } 1011 1012 function holdCall(type, args) { 1013 heldCalls.push({ 1014 type: type, 1015 args: Array.prototype.slice.call(args) 1016 }); 1017 1018 _logBlocked('[Privacy] Held tracker call until consent â', type); 1019 } 1020 1021 function holdSocket(url, protocols) { 1022 for (var i = 0; i < heldSockets.length; i++) { 1023 if (heldSockets[i].url === url) return; 1024 } 1025 1026 heldSockets.push({ 1027 url: url, 1028 protocols: protocols 1029 }); 1030 1031 _logBlocked('[Privacy] Held analytics WebSocket until consent â', url); 1032 } 1033 1034 function blockOrHoldScript(src, node, parent, method, refNode) { 1035 if (!shouldBlockScript(src)) return false; 1036 1037 if (shouldHold()) { 1038 holdScript(src, node, parent, method, refNode); 1039 return true; 1040 } 1041 1042 if (shouldDrop()) { 1043 _logBlocked('[Privacy] Blocked tracker script â', src); 1044 return true; 1045 } 1046 1047 return false; 1048 } 1049 1050 function blockOrHoldCall(type, args) { 1051 if (trackingAllowed()) return false; 1052 1053 if (shouldHold()) { 1054 holdCall(type, args); 1055 return true; 1056 } 1057 1058 if (shouldDrop()) { 1059 _logBlocked('[Privacy] Blocked tracker call â', type); 1060 return true; 1061 } 1062 1063 return false; 1064 } 1065 1066 var origCreateElement = document.createElement; 1067 var proto = Element.prototype; 1068 var origAppend = proto.appendChild; 1069 var origInsert = proto.insertBefore; 1070 var origAppendMethod = proto.append; 1071 var origPrependMethod = proto.prepend; 1072 var OrigWebSocket = window.WebSocket || null; 1073 1074 function cloneScriptForRelease(original) { 1075 var clone = origCreateElement.call(document, 'script'); 1076 1077 for (var i = 0; i < original.attributes.length; i++) { 1078 var attr = original.attributes[i]; 1079 1080 if (attr.name === 'src') { 1081 continue; 1082 } 1083 1084 try { 1085 clone.setAttribute(attr.name, attr.value); 1086 } catch (e) { } 1087 } 1088 1089 clone.src = original.src; 1090 1091 return clone; 1092 } 1093 1094 function holdStaticScriptTag(script) { 1095 if (!script || !script.src) return false; 1096 1097 var src = script.src; 1098 1099 if (!shouldBlockScript(src)) { 1100 return false; 1101 } 1102 1103 for (var i = 0; i < heldScripts.length; i++) { 1104 if (heldScripts[i].src === src) { 1105 return true; 1106 } 1107 } 1108 1109 if (shouldHold()) { 1110 heldScripts.push({ 1111 src: src, 1112 node: cloneScriptForRelease(script), 1113 parent: document.head || document.documentElement, 1114 method: 'appendChild', 1115 refNode: null 1116 }); 1117 1118 try { 1119 if (script.parentNode) { 1120 script.parentNode.removeChild(script); 1121 } 1122 } catch (e) { } 1123 1124 _logBlocked('[Privacy] Held static tracker script until consent â', src); 1125 return true; 1126 } 1127 1128 if (shouldDrop()) { 1129 try { 1130 if (script.parentNode) { 1131 script.parentNode.removeChild(script); 1132 } 1133 } catch (e) { } 1134 1135 _logBlocked('[Privacy] Removed static tracker script â', src); 1136 return true; 1137 } 1138 1139 return false; 1140 } 1141 1142 function scanStaticTrackerScripts() { 1143 var scripts = document.querySelectorAll('script[src]'); 1144 1145 for (var i = 0; i < scripts.length; i++) { 1146 holdStaticScriptTag(scripts[i]); 1147 } 1148 } 1149 1150 function restoreDataLayerPush() { 1151 window.dataLayer = window.dataLayer || []; 1152 1153 try { 1154 window.dataLayer.push = Array.prototype.push; 1155 } catch (e) { } 1156 } 1157 1158 function releaseHeldScripts() { 1159 var scripts = heldScripts.slice(); 1160 heldScripts = []; 1161 1162 for (var i = 0; i < scripts.length; i++) { 1163 var item = scripts[i]; 1164 1165 try { 1166 var node = item.node; 1167 1168 if (!node) { 1169 node = origCreateElement.call(document, 'script'); 1170 node.src = item.src; 1171 node.async = true; 1172 } 1173 1174 if (item.method === 'insertBefore' && item.parent && item.refNode) { 1175 origInsert.call(item.parent, node, item.refNode); 1176 } else if (item.parent) { 1177 origAppend.call(item.parent, node); 1178 } else { 1179 origAppend.call(document.head || document.documentElement, node); 1180 } 1181 1182 _logBlocked('[Privacy] Released tracker script after consent â', item.src); 1183 } catch (e) { 1184 _logBlocked('[Privacy] Failed to release tracker script â', item.src, e); 1185 } 1186 } 1187 } 1188 1189 function releaseHeldSockets() { 1190 var sockets = heldSockets.slice(); 1191 heldSockets = []; 1192 1193 if (!OrigWebSocket) return; 1194 1195 for (var i = 0; i < sockets.length; i++) { 1196 try { 1197 if (sockets[i].protocols !== undefined) { 1198 new OrigWebSocket(sockets[i].url, sockets[i].protocols); 1199 } else { 1200 new OrigWebSocket(sockets[i].url); 1201 } 1202 1203 _logBlocked('[Privacy] Released analytics WebSocket after consent â', sockets[i].url); 1204 } catch (e) { 1205 _logBlocked('[Privacy] Failed to release analytics WebSocket â', sockets[i].url, e); 1206 } 1207 } 1208 } 1209 1210 function replayHeldCalls() { 1211 var calls = heldCalls.slice(); 1212 heldCalls = []; 1213 1214 restoreDataLayerPush();
1215 1216 for (var i = 0; i < calls.length; i++) { 1217 var item = calls[i]; 1218 1219 try { 1220 if (item.type === 'dataLayer.push') { 1221 Array.prototype.push.apply(window.dataLayer, item.args); 1222 continue; 1223 } 1224 1225 if (item.type === 'gtag' || item.type === 'google_tracking.gtag') { 1226 window.dataLayer = window.dataLayer || []; 1227 Array.prototype.push.call(window.dataLayer, item.args); 1228 continue; 1229 } 1230 1231 if ((item.type === 'fbq' || item.type === 'facebook_tracking.fbq') && typeof window.fbq === 'function') { 1232 window.fbq.apply(window, item.args); 1233 continue; 1234 } 1235 1236 if (item.type === 'analytics.track' && window.analytics && typeof window.analytics.track === 'function') { 1237 window.analytics.track.apply(window.analytics, item.args); 1238 continue; 1239 } 1240 1241 if (item.type === 'analytics.identify' && window.analytics && typeof window.analytics.identify === 'function') { 1242 window.analytics.identify.apply(window.analytics, item.args); 1243 continue; 1244 } 1245 1246 if (item.type === 'analytics.page' && window.analytics && typeof window.analytics.page === 'function') { 1247 window.analytics.page.apply(window.analytics, item.args); 1248 continue; 1249 } 1250 1251 if (item.type === 'mixpanel.track' && window.mixpanel && typeof window.mixpanel.track === 'function') { 1252 window.mixpanel.track.apply(window.mixpanel, item.args); 1253 continue; 1254 } 1255 1256 if (item.type === 'amplitude.logEvent' && window.amplitude && typeof window.amplitude.logEvent === 'function') { 1257 window.amplitude.logEvent.apply(window.amplitude, item.args); 1258 continue; 1259 } 1260 1261 if (item.type === 'plausible' && typeof window.plausible === 'function') { 1262 window.plausible.apply(window, item.args); 1263 continue; 1264 } 1265 } catch (e) { 1266 _logBlocked('[Privacy] Failed to replay tracker call â', item.type, e); 1267 } 1268 } 1269 } 1270 1271 function acceptTracking() { 1272 if (gpcEnabled) { 1273 _logBlocked('[Privacy] Consent grant ignored because GPC/DNT is enabled.'); 1274 return; 1275 } 1276 1277 consent = 'granted'; 1278 setStoredConsent('granted'); 1279 1280 // *** ADDED â Consent Mode v2 update signal *** 1281 // Use the real gtag() function (defined before the IIFE) so gtag.js 1282 // registers consent via its API. Must fire before releaseHeldScripts 1283 // so GA loads already knowing consent is granted. 1284 try { 1285 if (typeof window.gtag === 'function') { 1286 console.log('[Privacy] Updating Consent Mode v2 signals for granted consent.'); 1287 window.gtag('consent', 'update', { 1288 analytics_storage: 'granted', 1289 ad_storage: 'granted', 1290 ad_user_data: 'granted', 1291 ad_personalization: 'granted' 1292 }); 1293 } 1294 } catch (e) { } 1295 // *** END ADDED â Consent Mode v2 update signal *** 1296 1297 restoreDataLayerPush(); 1298 releaseHeldScripts(); 1299 releaseHeldSockets(); 1300 1301 setTimeout(function () { 1302 replayHeldCalls(); 1303 }, 800); 1304 1305 _logBlocked('[Privacy] Tracking accepted. Held trackers released.'); 1306 } 1307 1308 function denyTracking() { 1309 consent = 'denied'; 1310 setStoredConsent('denied'); 1311 1312 heldScripts = []; 1313 heldCalls = []; 1314 heldSockets = []; 1315 1316 _logBlocked('[Privacy] Tracking denied. Held trackers discarded.'); 1317 } 1318 1319 var privacy = { 1320 gpcEnabled: gpcEnabled, 1321 1322 get consent() { 1323 return getConsentState(); 1324 }, 1325 1326 get trackingAllowed() { 1327 return trackingAllowed(); 1328 }, 1329 1330 get heldScripts() { 1331 return heldScripts.slice(); 1332 }, 1333 1334 get heldCalls() { 1335 return heldCalls.slice(); 1336 }, 1337 1338 get heldSockets() { 1339 return heldSockets.slice(); 1340 }, 1341 1342 setConsent: function (value) { 1343 if (value === 'granted') { 1344 acceptTracking(); 1345 return; 1346 } 1347 1348 if (value === 'denied') { 1349 denyTracking(); 1350 } 1351 }, 1352 1353 accept: acceptTracking, 1354 deny: denyTracking, 1355 release: acceptTracking, 1356 1357 reset: function () { 1358 clearStoredConsent(); 1359 consent = null; 1360 heldScripts = []; 1361 heldCalls = []; 1362 heldSockets = []; 1363 } 1364 }; 1365 1366 window.DiPrivacy = privacy; 1367 1368 // *** ADDED â Early return for granted users *** 1369 // If user has already granted consent, skip installing all the stubs and blockers. 1370 // The Consent Mode v2 default + update was already pushed via the real gtag() 1371 // before the IIFE started, so GTM/GA will read granted state on init. 1372 if (trackingAllowed()) { 1373 return; 1374 } 1375 // *** END ADDED â Early return for granted users *** 1376 1377 console.log('[Privacy] Tracking is held/blocked. Consent state â', getConsentState()); 1378 1379 // sendBeacon is commonly used for analytics hits and page-unload beacons. 1380 if (navigator.sendBeacon) { 1381 var origBeacon = navigator.sendBeacon; 1382 1383 navigator.sendBeacon = function (url, data) { 1384 if (!trackingAllowed() && shouldBlockUrl(url)) { 1385 _logBlocked('[Privacy] Blocked sendBeacon â', url); 1386 return true; 1387 } 1388 1389 return origBeacon.apply(this, arguments); 1390 }; 1391 } 1392
1393 // fetch() is commonly used by modern analytics, CDPs, pixels, and session replay tools. 1394 if (window.fetch) { 1395 var origFetch = window.fetch; 1396 1397 window.fetch = function (input, init) { 1398 var url = (typeof input === 'string') ? input : (input && input.url); 1399 1400 if (!trackingAllowed() && shouldBlockUrl(url)) { 1401 _logBlocked('[Privacy] Blocked fetch â', url); 1402 return Promise.resolve(new Response(null, { status: 204 })); 1403 } 1404 1405 return origFetch.apply(this, arguments).catch(function () { 1406 return new Response(null, { status: 204 }); 1407 }); 1408 }; 1409 } 1410 1411 // XHR is still used by older analytics and marketing scripts. 1412 if (window.XMLHttpRequest) { 1413 var OrigXHR = XMLHttpRequest; 1414 1415 function WrappedXHR() { 1416 var xhr = new OrigXHR(); 1417 var _open = xhr.open; 1418 var _send = xhr.send; 1419 1420 xhr.open = function (method, url) { 1421 xhr._blockedUrl = url; 1422 xhr._blockThis = !trackingAllowed() && shouldBlockUrl(url); 1423 return _open.apply(xhr, arguments); 1424 }; 1425 1426 xhr.send = function () { 1427 if (xhr._blockThis) { 1428 _logBlocked('[Privacy] Blocked XHR â', xhr._blockedUrl); 1429 1430 try { 1431 xhr.abort(); 1432 } catch (e) { } 1433 1434 return; 1435 } 1436 1437 return _send.apply(xhr, arguments); 1438 }; 1439 1440 return xhr; 1441 } 1442 1443 WrappedXHR.prototype = OrigXHR.prototype; 1444 window.XMLHttpRequest = WrappedXHR; 1445 } 1446 1447 // Some trackers still fire using image beacon URLs. 1448 if (window.HTMLImageElement) { 1449 var imgProto = HTMLImageElement.prototype; 1450 var desc = Object.getOwnPropertyDescriptor(imgProto, 'src'); 1451 1452 if (desc && desc.set) { 1453 Object.defineProperty(imgProto, 'src', { 1454 get: desc.get, 1455 set: function (url) { 1456 if (!trackingAllowed() && shouldBlockUrl(url)) { 1457 _logBlocked('[Privacy] Blocked Image â', url); 1458 return url; 1459 } 1460 1461 return desc.set.call(this, url); 1462 }, 1463 configurable: true, 1464 enumerable: true 1465 }); 1466 } 1467 } 1468 1469 // Consent-gated WebSockets. Only matching socketPatterns are held before consent. 1470 if (OrigWebSocket) { 1471 window.WebSocket = function (url, protocols) { 1472 if (!trackingAllowed() && shouldHoldSocket(url)) { 1473 if (shouldHold()) { 1474 holdSocket(url, protocols); 1475 } else { 1476 _logBlocked('[Privacy] Blocked analytics WebSocket â', url); 1477 } 1478 1479 return { 1480 close: function () { }, 1481 send: function () { }, 1482 addEventListener: function () { }, 1483 removeEventListener: function () { }, 1484 dispatchEvent: function () { return false; }, 1485 readyState: 3, 1486 bufferedAmount: 0, 1487 url: String(url || '') 1488 }; 1489 } 1490 1491 if (protocols !== undefined) { 1492 return new OrigWebSocket(url, protocols); 1493 } 1494 1495 return new OrigWebSocket(url); 1496 }; 1497 1498 window.WebSocket.prototype = OrigWebSocket.prototype; 1499 } 1500 1501 // jQuery.getScript() is used by many older site snippets to load trackers dynamically. 1502 if (window.jQuery && window.jQuery.getScript) { 1503 var $ = window.jQuery; 1504 var origGetScript = $.getScript; 1505 1506 $.getScript = function (url, cb) { 1507 if (blockOrHoldScript(url, null, null, 'jqueryGetScript', null)) { 1508 return; 1509 } 1510 1511 return origGetScript.apply(this, arguments); 1512 }; 1513 } 1514 1515 // Dynamically inserted <script src> trackers are held or blocked. 1516 (function () { 1517 proto.appendChild = function (node) { 1518 if (node && node.tagName === 'SCRIPT') { 1519 var src = node.src || node.getAttribute('src'); 1520 1521 if (blockOrHoldScript(src, node, this, 'appendChild', null)) { 1522 return node; 1523 } 1524 } 1525 1526 return origAppend.call(this, node); 1527 }; 1528 1529 proto.insertBefore = function (node, ref) { 1530 if (node && node.tagName === 'SCRIPT') { 1531 var src = node.src || node.getAttribute('src'); 1532 1533 if (blockOrHoldScript(src, node, this, 'insertBefore', ref)) { 1534 return node; 1535 } 1536 } 1537 1538 return origInsert.call(this, node, ref); 1539 }; 1540 1541 if (origAppendMethod) { 1542 proto.append = function () { 1543 for (var i = 0; i < arguments.length; i++) { 1544 var node = arguments[i]; 1545 1546 if (node && node.tagName === 'SCRIPT') { 1547 var src = node.src || node.getAttribute('src'); 1548 1549 if (blockOrHoldScript(src, node, this, 'appendChild', null)) { 1550 return; 1551 } 1552 } 1553 } 1554 1555 return origAppendMethod.apply(this, arguments); 1556 }; 1557 } 1558 1559 if (origPrependMethod) { 1560 proto.prepend = function () { 1561 for (var i = 0; i < arguments.length; i++) { 1562 var node = arguments[i]; 1563 1564 if (node && node.tagName === 'SCRIPT') { 1565 var src = node.src || node.getAttribute('src'); 1566 1567 if (blockOrHoldScript(src, node, this, 'appendChild', null)) { 1568 return; 1569 } 1570 } 1571 } 1572 1573 return origPrependMethod.apply(this, arguments); 1574 }; 1575 } 1576 })(); 1577 1578 // Static/parser-inserted tracker scripts are scanned separately. 1579 // This helps catch hard-coded <script src="..."> tags that are not loaded through appendChild/insertBefore. 1580 scanStaticTrackerScripts(); 1581 1582 if (document.readyState === 'loading') { 1583 document.addEventListener('DOMContentLoaded', scanStaticTrackerScripts); 1584 } 1585 1586 // Watch for tracker scripts added later by widgets or inline code. 1587 // This catches scripts that are inserted into the page after our initial scan. 1588 if (window.MutationObserver) { 1589 try { 1590 var trackerScriptObserver = new MutationObserver(function (mutations) { 1591 for (var i = 0; i < mutations.length; i++) { 1592 var added = mutations[i].addedNodes; 1593 1594 for (var j = 0; j < added.length; j++) { 1595 var node = added[j]; 1596 1597 if (node && node.tagName === 'SCRIPT') { 1598 holdStaticScriptTag(node); 1599 } 1600 1601 if (node && node.querySelectorAll) { 1602 var scripts = node.querySelectorAll('script[src]'); 1603 1604 for (var k = 0; k < scripts.length; k++) { 1605 holdStaticScriptTag(scripts[k]); 1606 } 1607 } 1608 } 1609 } 1610 }); 1611 1612 trackerScriptObserver.observe(document.documentElement, { 1613 childList: true, 1614 subtree: true 1615 }); 1616 } catch (e) { } 1617 } 1618 1619 function trackerStub(name) { 1620 return function () { 1621 blockOrHoldCall(name, arguments); 1622 }; 1623 } 1624 1625 /* 1626 Common tracker APIs. 1627 1628 These stubs prevent known trackers from firing before consent. 1629 When consent is pending, calls are held. When consent is denied or GPC/DNT 1630 is active, calls are discarded. When accepted, held calls are replayed. 1631 */ 1632 window.dataLayer = window.dataLayer || []; 1633 1634 window.dataLayer.push = function () {
1635 // *** ADDED â Consent Mode v2 / GTM / gtag bypass *** 1636 // Allow consent signals, GTM internals, and gtag config/js/set/get 1637 // through unconditionally. GTM needs unobstructed dataLayer.push 1638 // to initialize itself and read consent state. Consent signals 1639 // must never be held or dropped. 1640 var arg = arguments[0]; 1641 1642 // Always allow consent signals through â Consent Mode v2 must not be gated. 1643 // gtag('consent', 'default'|'update', {...}) arrives as arguments where arg[0] === 'consent'. 1644 var isConsentCommand = arg && ( 1645 arg[0] === 'consent' || 1646 arg['0'] === 'consent' 1647 ); 1648 1649 // Always allow GTM internal events through (gtm.start, gtm.js, gtm.dom, gtm.load, etc.) 1650 var isGtmInternal = arg && typeof arg === 'object' && !Array.isArray(arg) && ( 1651 'gtm.start' in arg || 1652 'gtm.uniqueEventId' in arg || 1653 (arg.event && typeof arg.event === 'string' && arg.event.indexOf('gtm.') === 0) 1654 ); 1655 1656 // Allow gtag config/js/set/get commands so gtag.js can initialize properly. 1657 // gtag('js', new Date()) and gtag('config', 'G-XXX') need to reach dataLayer. 1658 var isGtagInternal = arg && ( 1659 arg[0] === 'js' || 1660 arg[0] === 'config' || 1661 arg[0] === 'set' || 1662 arg[0] === 'get' || 1663 arg['0'] === 'js' || 1664 arg['0'] === 'config' || 1665 arg['0'] === 'set' || 1666 arg['0'] === 'get' 1667 ); 1668 1669 if (isConsentCommand || isGtmInternal || isGtagInternal) { 1670 return Array.prototype.push.apply(window.dataLayer, arguments); 1671 } 1672 // *** END ADDED â Consent Mode v2 / GTM / gtag bypass *** 1673 1674 if (blockOrHoldCall('dataLayer.push', arguments)) { 1675 return; 1676 } 1677 1678 return Array.prototype.push.apply(window.dataLayer, arguments); 1679 }; 1680 1681 // *** ADDED â gtag stubs intentionally not installed *** 1682 // NOTE: window.gtag and window.google_tracking.gtag were defined as real 1683 // functions BEFORE this IIFE ran (in the Consent Mode v2 bootstrap block). 1684 // We do NOT overwrite them with stubs here â they need to remain real so 1685 // that gtag.js can register consent via its API (which is what produces 1686 // pscdl=denied/granted on hits instead of pscdl=noapi). 1687 // 1688 // Tracking gating for gtag still happens via the wrapped dataLayer.push 1689 // above: consent/gtm/gtag-internal commands pass through, everything else 1690 // (e.g. gtag('event', 'conversion', {...})) goes through blockOrHoldCall. 1691 // *** END ADDED â gtag stubs intentionally not installed *** 1692 1693 window.fbq = trackerStub('fbq'); 1694 window.facebook_tracking = window.facebook_tracking || {}; 1695 window.facebook_tracking.fbq = trackerStub('facebook_tracking.fbq'); 1696 1697 window.FB = window.FB || { AppEvents: {} }; 1698 window.FB.AppEvents.logEvent = trackerStub('FB.AppEvents.logEvent'); 1699 window.FB.AppEvents.logPurchase = trackerStub('FB.AppEvents.logPurchase'); 1700 1701 window.twttr = window.twttr || {}; 1702 window.twttr.track = trackerStub('twttr.track'); 1703 window.twttr.load = trackerStub('twttr.load'); 1704 1705 window._linkedin_data_partner_ids = []; 1706 window.linkedinData = trackerStub('linkedinData'); 1707 1708 window.pintrk = trackerStub('pintrk'); 1709 window.ttq = trackerStub('ttq'); 1710 window.snaptr = trackerStub('snaptr'); 1711 window.hj = trackerStub('hj'); 1712 1713 window.mixpanel = window.mixpanel || {}; 1714 window.mixpanel.track = trackerStub('mixpanel.track'); 1715 window.mixpanel.identify = trackerStub('mixpanel.identify'); 1716 window.mixpanel.register = trackerStub('mixpanel.register'); 1717 1718 window.amplitude = window.amplitude || {}; 1719 window.amplitude.logEvent = trackerStub('amplitude.logEvent'); 1720 window.amplitude.setUserProperties = trackerStub('amplitude.setUserProperties'); 1721 1722 window.analytics = window.analytics || {}; 1723 window.analytics.track = trackerStub('analytics.track'); 1724 window.analytics.identify = trackerStub('analytics.identify'); 1725 window.analytics.page = trackerStub('analytics.page'); 1726 1727 window.plausible = trackerStub('plausible'); 1728 1729})(window, document); 1730 1731(function () { 1732 1733 // Set window.privacy_allow = { disabled: true } before this script to suppress the banner entirely 1734 var privacy_allow = Object.assign({ disabled: false }, window.privacy_allow || {}); 1735 window.privacy_allow = privacy_allow; 1736 1737 // Per-site override API â call before or after this script: 1738 // privacy_config.set('variant', 'manage'); 1739 if (!window.privacy_config || typeof window.privacy_config !== 'object') window.privacy_config = {}; 1740 Object.defineProperty(window.privacy_config, 'set', { 1741 enumerable: false, configurable: true, 1742 value: function (key, val) { window.privacy_config[key] = val; } 1743 }); 1744 1745 // variant: 'simple' â Decline / Accept All banner (no modal) 1746 // variant: 'manage' â Manage / Accept All banner + preferences modal 1747 var privacy_config_defaults = { 1748 primaryColor: '#1e3a5f', 1749 bgColor: '#ffffff', 1750 textColor: '#374151', 1751 fontFamily: 'inherit', 1752 buttonRadius: '4px', 1753 privacyPolicyUrl: '/privacy/', 1754 variant: 'manage', 1755 bannerText: 'We use cookies and tracking technologies to improve your browsing experience on our website, to show you personalized content, and to analyze our website traffic. Read our [privacy policy] to learn more.', 1756 declineLabel: 'Decline', 1757 manageLabel: 'Manage', 1758 acceptLabel: 'Accept All', 1759 modalTitle: 'Your tracking preferences.',
1760 modalDesc: 'We use tracking technologies, including scripts that may not set cookies, to understand how you use our site and deliver relevant content. You can learn more in our', 1761 submitLabel: 'Submit Preferences', 1762 rejectLabel: 'Reject all', 1763 cancelLabel: 'Cancel', 1764 categories: [ 1765 { 1766 id: 'tracking', 1767 label: 'Tracking & Analytics', 1768 description: 'Scripts and technologies that help us understand how visitors interact with our site, measure performance, and serve relevant content and ads.', 1769 defaultOn: true, 1770 } 1771 ] 1772 }; 1773 var privacy_config; 1774 1775 // âââ helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1776 1777 function privacy_cb_el(tag, props) { 1778 var node = document.createElement(tag); 1779 if (props) Object.keys(props).forEach(function (k) { node[k] = props[k]; }); 1780 return node; 1781 } 1782 1783 function privacy_cb_removeBanner() { 1784 var el = document.getElementById('di-consent-banner'); 1785 if (el) el.parentNode.removeChild(el); 1786 } 1787 1788 function privacy_cb_removeModal() { 1789 var el = document.getElementById('di-cookie-pref-dialog'); 1790 if (!el) return; 1791 try { el.close(); } catch (e) { } 1792 } 1793 1794 function privacy_cb_accept() { 1795 try { if (window.DiPrivacy && typeof window.DiPrivacy.accept === 'function') window.DiPrivacy.accept(); } catch (e) { } 1796 privacy_cb_removeModal(); privacy_cb_removeBanner(); 1797 } 1798 1799 function privacy_cb_reject() { 1800 try { if (window.DiPrivacy && typeof window.DiPrivacy.deny === 'function') window.DiPrivacy.deny(); } catch (e) { } 1801 privacy_cb_removeModal(); privacy_cb_removeBanner(); 1802 } 1803 1804 function privacy_cb_rejectAll() { 1805 privacy_config.categories.forEach(function (cat) { 1806 var t = document.getElementById('di-cm-' + cat.id + '-toggle'); 1807 if (t) t.checked = false; 1808 }); 1809 privacy_cb_reject(); 1810 } 1811 1812 function privacy_cb_savePreferences() { 1813 var anyOff = privacy_config.categories.some(function (cat) { 1814 var t = document.getElementById('di-cm-' + cat.id + '-toggle'); 1815 return t && !t.checked; 1816 }); 1817 anyOff ? privacy_cb_reject() : privacy_cb_accept(); 1818 } 1819 1820 // âââ styles ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1821 1822 function privacy_cb_injectStyles() { 1823 var existing = document.getElementById('di-consent-banner-styles'); 1824 if (existing) existing.parentNode.removeChild(existing); 1825 var style = document.createElement('style'); 1826 style.id = 'di-consent-banner-styles'; 1827 var css = ''; 1828 1829 css += '#di-consent-banner{position:fixed;bottom:0;left:0;right:0;z-index:9999999999;background:var(--di-cb-bg);box-shadow:0 -4px 16px rgba(0,0,0,.10);padding:20px 24px;font-family:var(--di-cb-font);box-sizing:border-box;}'; 1830 css += '#di-consent-banner .di-cb__inner{display:flex;flex-direction:column;align-items:center;justify-content:space-between;gap:32px;max-width:1280px;margin:0 auto;}'; 1831 css += '@media(min-width:640px){#di-consent-banner .di-cb__inner{flex-direction:row;}}'; 1832 css += '#di-consent-banner .di-cb__text{font-size:14px;color:var(--di-cb-text);margin:0;}'; 1833 css += '#di-consent-banner .di-cb__learn{color:var(--di-cb-primary);text-decoration:underline;transition:opacity .2s ease;}'; 1834 css += '#di-consent-banner .di-cb__learn:hover{opacity:.8;}'; 1835 css += '#di-consent-banner .di-cb__actions{display:flex;gap:8px!important;flex-shrink:0;}'; 1836 css += '@media(max-width:639px){#di-consent-banner .di-cb__actions{width:100%;flex-direction:column-reverse;}}'; 1837 css += '#di-consent-banner .di-cb__btn{cursor:pointer;font-size:16px;font-weight:500;padding:12px 24px;border-radius:var(--di-cb-radius);border:1px solid transparent;line-height:1;white-space:nowrap;transition:background .2s ease,color .2s ease,opacity .2s ease;}'; 1838 css += '#di-consent-banner .di-cb__btn--secondary{background:transparent;border-color:var(--di-cb-primary);color:var(--di-cb-primary);}'; 1839 css += '#di-consent-banner .di-cb__btn--secondary:hover{background:var(--di-cb-primary);color:#fff;}'; 1840 css += '#di-consent-banner .di-cb__btn--primary{background:var(--di-cb-primary);border-color:var(--di-cb-primary);color:#fff;}'; 1841 css += '#di-consent-banner .di-cb__btn--primary:hover{opacity:.88;}'; 1842 1843 css += '@keyframes di-cm-fadein{from{opacity:0}
1843to{opacity:1}}'; 1844 css += '@keyframes di-cm-slidein{from{transform:translateY(10px);opacity:0}to{transform:translateY(0);opacity:1}}'; 1845 css += '#di-cookie-pref-dialog{border:none;padding:0;margin:auto;background:#fff;border-radius:10px;box-shadow:0 8px 40px rgba(0,0,0,.22);max-width:480px;width:calc(100vw - 32px);box-sizing:border-box;overflow:hidden;font-family:var(--di-cb-font);animation:di-cm-slidein .2s ease;}'; 1846 css += '#di-cookie-pref-dialog::backdrop{background:rgba(0,0,0,.45);animation:di-cm-fadein .18s ease;}'; 1847 css += '#di-cookie-pref-dialog .di-cm__header{padding:22px 24px 0;}'; 1848 css += '#di-cookie-pref-dialog .di-cm__title-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;}'; 1849 css += '#di-cookie-pref-dialog .di-cm__title{font-size:19px;font-weight:700;color:#111827;margin:0 0 8px;line-height:1.3;}'; 1850 css += '#di-cookie-pref-dialog .di-cm__close{background:none;border:none;cursor:pointer;color:#9ca3af;font-size:22px;line-height:1;padding:0;flex-shrink:0;margin-top:2px;transition:color .15s ease;}'; 1851 css += '#di-cookie-pref-dialog .di-cm__close:hover{color:#374151;}'; 1852 css += '#di-cookie-pref-dialog .di-cm__desc{font-size:13px;color:#6b7280;margin:0;line-height:1.55;padding:0 24px;}'; 1853 css += '#di-cookie-pref-dialog .di-cm__desc a{color:var(--di-cb-primary);text-decoration:underline;}'; 1854 css += '#di-cookie-pref-dialog .di-cm__rows{margin-top:16px;border-top:1px solid #f3f4f6;}'; 1855 css += '#di-cookie-pref-dialog .di-cm__item{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:16px 24px;border-bottom:1px solid #f3f4f6;}'; 1856 css += '#di-cookie-pref-dialog .di-cm__item-info{flex:1;}'; 1857 css += '#di-cookie-pref-dialog .di-cm__item-label{font-size:14px;font-weight:600;color:#111827;margin:0 0 4px;}'; 1858 css += '#di-cookie-pref-dialog .di-cm__item-desc{font-size:12px;color:#6b7280;margin:0;line-height:1.5;}'; 1859 css += '#di-cookie-pref-dialog .di-cm__toggle-wrap{flex-shrink:0;position:relative;width:44px;height:24px;margin-top:2px;}'; 1860 css += '#di-cookie-pref-dialog .di-cm__toggle-wrap input{opacity:0;width:0;height:0;position:absolute;}'; 1861 css += '#di-cookie-pref-dialog .di-cm__toggle-track{position:absolute;inset:0;background:#d1d5db;border-radius:999px;cursor:pointer;transition:background .2s ease;}'; 1862 css += '#di-cookie-pref-dialog .di-cm__toggle-track::after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2);transition:transform .2s ease;}'; 1863 css += '#di-cookie-pref-dialog .di-cm__toggle-wrap input:checked+.di-cm__toggle-track{background:var(--di-cb-primary);}'; 1864 css += '#di-cookie-pref-dialog .di-cm__toggle-wrap input:checked+.di-cm__toggle-track::after{transform:translateX(20px);}'; 1865 css += '#di-cookie-pref-dialog .di-cm__footer{display:flex;gap:8px;padding:16px 24px;flex-wrap:wrap;}'; 1866 css += '#di-cookie-pref-dialog .di-cm__btn{cursor:pointer;font-size:14px;font-weight:500;padding:10px 16px;border-radius:var(--di-cb-radius);border:1px solid transparent;line-height:1;white-space:nowrap;flex:1;transition:background .2s ease,color .2s ease,opacity .2s ease;}'; 1867 css += '#di-cookie-pref-dialog .di-cm__btn--cancel{background:transparent;border-color:#d1d5db;color:#374151;}'; 1868 css += '#di-cookie-pref-dialog .di-cm__btn--cancel:hover{background:#f9fafb;}'; 1869 css += '#di-cookie-pref-dialog .di-cm__btn--reject{background:transparent;border-color:var(--di-cb-primary);color:var(--di-cb-primary);}'; 1870 css += '#di-cookie-pref-dialog .di-cm__btn--reject:hover{background:var(--di-cb-primary);color:#fff;}'; 1871 css += '#di-cookie-pref-dialog .di-cm__btn--submit{background:var(--di-cb-primary);border-color:var(--di-cb-primary);color:#fff;}'; 1872 css += '#di-cookie-pref-dialog .di-cm__btn--submit:hover{opacity:.88;}'; 1873 css += '#di-cookie-pref-dialog .di-cm__policy{padding:0 24px 16px;font-size:11px;color:#9ca3af;}'; 1874 css += '#di-cookie-pref-dialog .di-cm__policy a{color:#9ca3af;text-decoration:underline;}'; 1875 css += '#di-cookie-pref-dialog .di-cm__policy a:hover{color:#6b7280;}'; 1876 1877 style.textContent = css; 1878 document.head.appendChild(style); 1879 } 1880 1881 // âââ modal âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1882 1883 function privacy_cb_createDialog() { 1884 var dialog = privacy_cb_el('dialog', { id: 'di-cookie-pref-dialog' }); 1885 dialog.style.setProperty('--di-cb-primary', privacy_config.primaryColor); 1886 dialog.style.setProperty('--di-cb-radius', privacy_config.buttonRadius); 1887 dialog.style.setProperty('--di-cb-font', privacy_config.fontFamily); 1888 1889 dialog.addEventListener('click', function (e) { 1890 var r = dialog.getBoundingClientRect(); 1891 if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) privacy_cb_removeModal(); 1892 }); 1893 dialog.addEventListener('cancel', function (e) { e.preventDefault(); privacy_cb_removeModal(); }); 1894 1895 var titleRow = privacy_cb_el('div', { className: 'di-cm__title-row' });
1896 var titleEl = privacy_cb_el('p', { className: 'di-cm__title', textContent: privacy_config.modalTitle }); 1897 var closeBtn = privacy_cb_el('button', { className: 'di-cm__close', type: 'button', innerHTML: '×' }); 1898 closeBtn.setAttribute('aria-label', 'Close'); 1899 closeBtn.addEventListener('click', privacy_cb_removeModal); 1900 titleRow.appendChild(titleEl); 1901 titleRow.appendChild(closeBtn); 1902 1903 var header = privacy_cb_el('div', { className: 'di-cm__header' }); 1904 header.appendChild(titleRow); 1905 1906 var desc = privacy_cb_el('p', { className: 'di-cm__desc' }); 1907 desc.appendChild(document.createTextNode(privacy_config.modalDesc + ' ')); 1908 desc.appendChild(privacy_cb_el('a', { href: privacy_config.privacyPolicyUrl, textContent: 'Privacy Policy' })); 1909 desc.appendChild(document.createTextNode('.')); 1910 1911 var rows = privacy_cb_el('div', { className: 'di-cm__rows' }); 1912 var toggleInputs = []; 1913 privacy_config.categories.forEach(function (cat) { 1914 var row = privacy_cb_el('div', { className: 'di-cm__item' }); 1915 var info = privacy_cb_el('div', { className: 'di-cm__item-info' }); 1916 var lbl = privacy_cb_el('p', { className: 'di-cm__item-label', textContent: cat.label }); 1917 var dsc = privacy_cb_el('p', { className: 'di-cm__item-desc', textContent: cat.description }); 1918 var wrap = privacy_cb_el('label', { className: 'di-cm__toggle-wrap' }); 1919 var input = privacy_cb_el('input', { type: 'checkbox', id: 'di-cm-' + cat.id + '-toggle', checked: cat.defaultOn !== false }); 1920 var track = privacy_cb_el('span', { className: 'di-cm__toggle-track' }); 1921 toggleInputs.push(input); 1922 info.appendChild(lbl); info.appendChild(dsc); 1923 wrap.appendChild(input); wrap.appendChild(track); 1924 row.appendChild(info); row.appendChild(wrap); 1925 rows.appendChild(row); 1926 }); 1927 1928 var footer = privacy_cb_el('div', { className: 'di-cm__footer' }); 1929 var cancelBtn = privacy_cb_el('button', { className: 'di-cm__btn di-cm__btn--cancel', type: 'button', textContent: privacy_config.cancelLabel }); 1930 var rejectBtn = privacy_cb_el('button', { className: 'di-cm__btn di-cm__btn--reject', type: 'button', textContent: privacy_config.rejectLabel }); 1931 rejectBtn.setAttribute('data-event', 'consent_reject'); 1932 var submitBtn = privacy_cb_el('button', { className: 'di-cm__btn di-cm__btn--submit', type: 'button', textContent: privacy_config.submitLabel }); 1933 function privacy_cb_updateSubmitEvent() { 1934 var anyOff = toggleInputs.some(function (t) { return !t.checked; }); 1935 submitBtn.setAttribute('data-event', anyOff ? 'consent_reject' : 'consent_accept'); 1936 } 1937 privacy_cb_updateSubmitEvent(); 1938 toggleInputs.forEach(function (t) { t.addEventListener('change', privacy_cb_updateSubmitEvent); }); 1939 cancelBtn.addEventListener('click', privacy_cb_removeModal); 1940 rejectBtn.addEventListener('click', privacy_cb_rejectAll); 1941 submitBtn.addEventListener('click', privacy_cb_savePreferences); 1942 footer.appendChild(cancelBtn); footer.appendChild(rejectBtn); footer.appendChild(submitBtn); 1943 1944 var policyFooter = privacy_cb_el('div', { className: 'di-cm__policy' }); 1945 policyFooter.appendChild(privacy_cb_el('a', { href: privacy_config.privacyPolicyUrl, textContent: 'Privacy Policy' })); 1946 1947 dialog.appendChild(header); 1948 dialog.appendChild(desc); 1949 dialog.appendChild(rows); 1950 dialog.appendChild(footer); 1951 dialog.appendChild(policyFooter); 1952 return dialog; 1953 } 1954 1955 function privacy_cb_openModal() { 1956 var dialog = document.getElementById('di-cookie-pref-dialog'); 1957 if (dialog) dialog.showModal(); 1958 } 1959 1960 // âââ banner ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1961 1962 function privacy_cb_createBanner() { 1963 var wrap = privacy_cb_el('div', { id: 'di-consent-banner' }); 1964 var inner = privacy_cb_el('div', { className: 'di-cb__inner' }); 1965 var p = privacy_cb_el('p', { className: 'di-cb__text' }); 1966 1967 var match = privacy_config.bannerText.match(/^([\s\S]*?)\[([^\]]+)\]([\s\S]*)$/); 1968 if (match) { 1969 p.appendChild(document.createTextNode(match[1])); 1970 p.appendChild(privacy_cb_el('a', { className: 'di-cb__learn', href: privacy_config.privacyPolicyUrl, textContent: match[2] })); 1971 p.appendChild(document.createTextNode(match[3])); 1972 } else { 1973 p.textContent = privacy_config.bannerText; 1974 } 1975 1976 var actions = privacy_cb_el('div', { className: 'di-cb__actions' });
1977 var secondary = privacy_config.variant === 'manage' 1978 ? privacy_cb_el('button', { className: 'di-cb__btn di-cb__btn--secondary', type: 'button', textContent: privacy_config.manageLabel }) 1979 : privacy_cb_el('button', { className: 'di-cb__btn di-cb__btn--secondary', type: 'button', textContent: privacy_config.declineLabel }); 1980 if (privacy_config.variant !== 'manage') secondary.setAttribute('data-event', 'consent_reject'); 1981 var acceptBtn = privacy_cb_el('button', { className: 'di-cb__btn di-cb__btn--primary', type: 'button', textContent: privacy_config.acceptLabel }); 1982 acceptBtn.setAttribute('data-event', 'consent_accept'); 1983 1984 secondary.addEventListener('click', privacy_config.variant === 'manage' ? privacy_cb_openModal : privacy_cb_reject); 1985 acceptBtn.addEventListener('click', privacy_cb_accept); 1986 1987 actions.appendChild(secondary); 1988 actions.appendChild(acceptBtn); 1989 inner.appendChild(p); 1990 inner.appendChild(actions); 1991 wrap.appendChild(inner); 1992 return wrap; 1993 } 1994 1995 // âââ init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1996 1997 function privacy_cb_init() { 1998 privacy_config = Object.assign({}, privacy_config_defaults, window.privacy_config || {}); 1999 if (window.privacy_allow.disabled === true) return; 2000 if (!window.DiPrivacy || window.DiPrivacy.consent !== 'pending' || window.DiPrivacy.gpcEnabled) return; 2001 privacy_cb_injectStyles(); 2002 document.body.appendChild(privacy_cb_createDialog()); 2003 var banner = privacy_cb_createBanner(); 2004 banner.style.setProperty('--di-cb-primary', privacy_config.primaryColor); 2005 banner.style.setProperty('--di-cb-bg', privacy_config.bgColor); 2006 banner.style.setProperty('--di-cb-text', privacy_config.textColor); 2007 banner.style.setProperty('--di-cb-font', privacy_config.fontFamily); 2008 banner.style.setProperty('--di-cb-radius', privacy_config.buttonRadius); 2009 document.body.appendChild(banner); 2010 } 2011 2012 document.readyState === 'loading' 2013 ? document.addEventListener('DOMContentLoaded', privacy_cb_init) 2014 : privacy_cb_init(); 2015 2016})();
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.