1(function() { 2 'use strict'; 3 4 var CONFIG = typeof imageProtectionVars !== 'undefined' ? imageProtectionVars : {}; 5 6 var LEVELS = { 7 relaxed: { 8 preemptiveModifier: false, 9 blurOnWindowBlur: false, 10 focusLossHeuristic: false, 11 periodicClipboardClear: false 12 }, 13 balanced: { 14 preemptiveModifier: true, 15 blurOnWindowBlur: true, 16 focusLossHeuristic: false, 17 periodicClipboardClear: false 18 }, 19 strict: { 20 preemptiveModifier: true, 21 blurOnWindowBlur: true, 22 focusLossHeuristic: true, 23 periodicClipboardClear: true 24 } 25 }; 26 27 class ImageProtection { 28 constructor() { 29 this.filteredImages = []; 30 this.filteredVideos = []; 31 this.bgImages = []; 32 this.pseudoImages = []; 33 this.observer = null; 34 35 this.message = CONFIG.message || ''; 36 this.showCredit = !!CONFIG.showCredit; 37 this.blurAmount = CONFIG.blurAmount ? parseInt(CONFIG.blurAmount, 10) : 10; 38 this.blurImages = !!CONFIG.blurImages; 39 this.blurVideos = !!CONFIG.blurVideos; 40 this.disableRightClick = !!CONFIG.disableRightClick; 41 42 var levelName = (CONFIG.protectionLevel || 'balanced').toLowerCase(); 43 this.levelName = LEVELS[levelName] ? levelName : 'balanced'; 44 this.level = LEVELS[this.levelName]; 45 46 this.neutralizeClipboard = !!CONFIG.neutralizeClipboard; 47 this.interceptScreenRecording = !!CONFIG.interceptScreenRecording; 48 this.printProtection = !!CONFIG.printProtection; 49 50 // Active blur "locks". The content is blurred while any lock is held. 51 this.blurLocks = new Set(); 52 this.modifierTimer = null; 53 this.lastModifierAt = 0; 54 this.clipboardInterval = null; 55 56 this.init(); 57 } 58 59 init() { 60 this.injectStyles(); 61 this.getImages(); 62 this.getVideos(); 63 this.setupEventListeners(); 64 this.setupImageProtection(); 65 this.setupVideoProtection(); 66 this.setupImageLinkProtection(); 67 this.setupMutationObserver(); 68 this.setupScreenRecordingGuard(); 69 this.setupPeriodicClipboardClear(); 70 } 71 72 injectStyles() { 73 if (document.getElementById('image-protection-styles')) { 74 return; 75 } 76 77 var css = ''; 78 79 if (this.blurImages) { 80 // Blur only the generated box. Filtering the host would also blur text on top of it. 81 css += '.image-protection-blur-before::before{filter:blur(' + this.blurAmount + 'px) !important;transition:none !important;}' + 82 '.image-protection-blur-after::after{filter:blur(' + this.blurAmount + 'px) !important;transition:none !important;}'; 83 } 84 85 if (this.printProtection) { 86 // Blank protected media when printing / saving to PDF. 87 css += '@media print {' + 88 '.image-protection-on, .image-protection-on * {' + 89 'visibility: hidden !important;' + 90 '}' + 91 '.image-protection-pseudo-before::before,' + 92 '.image-protection-pseudo-after::after{' + 93 'background-image:none !important;content:none !important;' + 94 '}' + 95 'body::after {' + 96 "content: 'Protected content is not available in print.';" + 97 'display: block; text-align: center; padding: 40px; font-size: 16px;' + 98 '}' + 99 '}'; 100 } 101 102 if (!css) { 103 return; 104 } 105 106 var style = document.createElement('style'); 107 style.id = 'image-protection-styles'; 108 style.textContent = css; 109 (document.head || document.documentElement).appendChild(style); 110 } 111 112 setupMutationObserver() { 113 this.observer = new MutationObserver((mutations) => { 114 var shouldUpdate = false; 115 116 for (var i = 0; i < mutations.length; i++) { 117 var mutation = mutations[i]; 118 119 if (mutation.type === 'attributes' && mutation.target) { 120 if (mutation.target.nodeName === 'IMG' || mutation.target.nodeName === 'VIDEO') { 121 shouldUpdate = true; 122 break; 123 } 124 continue; 125 } 126 127 if (mutation.addedNodes && mutation.addedNodes.length) { 128 mutation.addedNodes.forEach((node) => { 129 if (node.nodeType !== Node.ELEMENT_NODE) { 130 return; 131 } 132 if (node.nodeName === 'IMG' || 133 node.nodeName === 'VIDEO' || 134 (node.getElementsByTagName && node.getElementsByTagName('img').length) || 135 (node.getElementsByTagName && node.getElementsByTagName('video').length) ||
136 this.hasBackgroundImage(node)) { 137 shouldUpdate = true; 138 } 139 }); 140 } 141 } 142 143 if (shouldUpdate) { 144 this.getImages(); 145 this.getVideos(); 146 this.setupImageProtection(); 147 this.setupVideoProtection(); 148 this.setupImageLinkProtection(); 149 this.refreshBlur(); 150 } 151 }); 152 153 this.observer.observe(document.body, { 154 childList: true, 155 subtree: true, 156 attributes: true, 157 attributeFilter: ['src', 'style', 'srcset'] 158 }); 159 } 160 161 backgroundImageIsUrl(style) { 162 return !!style && style.backgroundImage !== 'none' && style.backgroundImage.includes('url('); 163 } 164 165 hasBackgroundImage(element) { 166 try { 167 if (this.backgroundImageIsUrl(window.getComputedStyle(element))) { 168 return true; 169 } 170 171 return this.pseudoHasBackgroundImage(element, '::before') || this.pseudoHasBackgroundImage(element, '::after'); 172 } catch (e) { 173 return false; 174 } 175 } 176 177 pseudoHasBackgroundImage(element, pseudo) { 178 try { 179 var style = window.getComputedStyle(element, pseudo); 180 181 // A pseudo-element is not painted when content computes to none. 182 if (!style || style.content === 'none' || style.content === 'normal') { 183 return false; 184 } 185 186 return this.backgroundImageIsUrl(style); 187 } catch (e) { 188 return false; 189 } 190 } 191 192 isProtectionExcluded(element) { 193 if (!element || element.nodeType !== Node.ELEMENT_NODE) { 194 return false; 195 } 196 197 if (element.matches('[data-image-protection="true"], .force-image-protection')) { 198 return false; 199 } 200 201 return element.closest('.no-image-protection, [data-image-protection="false"]') !== null; 202 } 203 204 isRecaptchaElement(element) { 205 if (!element || element.nodeType !== Node.ELEMENT_NODE) { 206 return false; 207 } 208 209 if (element.tagName === 'IFRAME') { 210 var src = (element.getAttribute('src') || '').toLowerCase(); 211 if (src.indexOf('recaptcha') !== -1) { 212 return true; 213 } 214 } 215 216 var node = element; 217 while (node && node.nodeType === Node.ELEMENT_NODE) { 218 var id = (node.id || '').toLowerCase(); 219 var className = (typeof node.className === 'string' ? node.className : (node.getAttribute('class') || '')).toLowerCase(); 220 221 if (id.indexOf('recaptcha') !== -1 || className.indexOf('recaptcha') !== -1) { 222 return true; 223 } 224 225 if (node.getAttribute('data-sitekey')) { 226 return true; 227 } 228 229 node = node.parentElement; 230 } 231 232 return false; 233 } 234 235 getImages() { 236 this.filteredImages = []; 237 this.bgImages = []; 238 this.pseudoImages = []; 239 240 if (this.blurImages) { 241 var images = document.querySelectorAll("img:not([src$='.svg'])"); 242 243 Array.from(document.querySelectorAll('*')).forEach((element) => { 244 if (this.isProtectionExcluded(element)) { 245 return; 246 } 247 248 var style = window.getComputedStyle(element); 249 var hasDataSrc = element.hasAttribute('data-src'); 250 var isLikelyImage = hasDataSrc && ( 251 element.tagName === 'IMG' || 252 element.classList.contains('lazy') || 253 element.classList.contains('lazyload') || 254 /\.(jpg|jpeg|png|gif|webp)$/i.test(element.getAttribute('data-src') || '') 255 ); 256 257 if (this.backgroundImageIsUrl(style) || isLikelyImage) { 258 this.bgImages.push(element); 259 } 260 261 if (this.pseudoHasBackgroundImage(element, '::before')) { 262 this.pseudoImages.push({ element: element, pseudo: '::before' }); 263 } 264 265 if (this.pseudoHasBackgroundImage(element, '::after')) { 266 this.pseudoImages.push({ element: element, pseudo: '::after' }); 267 } 268 }); 269 270 var allImages = [...images, ...this.bgImages]; 271 this.filteredImages = allImages.filter((img) => !this.isProtectionExcluded(img)); 272 this.syncPseudoClasses(); 273 } 274 275 var forcedProtectionElements = document.querySelectorAll('[data-image-protection="true"], .force-image-protection'); 276 forcedProtectionElements.forEach((el) => { 277 if (!this.filteredImages.includes(el)) { 278 this.filteredImages.push(el); 279 } 280 }); 281 } 282 283 getVideos() { 284 this.filteredVideos = []; 285 286 if (this.blurVideos) { 287 var videos = document.querySelectorAll('video'); 288 this.filteredVideos = Array.from(videos).filter((video) => !this.isProtectionExcluded(video)); 289 } 290 291 var forcedProtectionVideos = document.querySelectorAll('video[data-image-protection="true"], video.force-image-protection'); 292 forcedProtectionVideos.forEach((video) => { 293 if (!this.filteredVideos.includes(video)) { 294 this.filteredVideos.push(video);
295 } 296 }); 297 } 298 299 setupEventListeners() { 300 if (this.disableRightClick) { 301 document.addEventListener('contextmenu', (e) => e.preventDefault(), true); 302 } 303 304 document.addEventListener('keydown', this.handleKeyDown.bind(this), true); 305 document.addEventListener('keyup', this.handleKeyUp.bind(this), true); 306 307 document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); 308 window.addEventListener('blur', this.handleWindowBlur.bind(this)); 309 window.addEventListener('focus', this.handleWindowFocus.bind(this)); 310 window.addEventListener('pagehide', () => this.addBlur('hidden')); 311 312 document.addEventListener('copy', this.handleCopy.bind(this)); 313 document.addEventListener('selectstart', this.handleSelectStart.bind(this)); 314 // Capture phase so a parent link cannot start a drag when the image itself is not draggable. 315 document.addEventListener('dragstart', this.handleDragStart.bind(this), true); 316 317 if (this.printProtection) { 318 window.addEventListener('beforeprint', () => this.addBlur('print')); 319 window.addEventListener('afterprint', () => this.removeBlur('print')); 320 } 321 } 322 323 // Layout-independent screenshot combo detection using e.code. 324 isScreenshotCombo(e) { 325 var code = e.code || ''; 326 327 // PrintScreen (Windows/Linux), and Shift+PrintScreen (Linux area capture). 328 if (code === 'PrintScreen' || e.key === 'PrintScreen' || e.keyCode === 44) { 329 return true; 330 } 331 332 // Windows Snipping Tool: Win+Shift+S (Meta maps to the Windows key). 333 if (e.shiftKey && e.metaKey && code === 'KeyS') { 334 return true; 335 } 336 337 // macOS: Cmd+Shift+3 / 4 / 5. 338 if (e.metaKey && e.shiftKey && (code === 'Digit3' || code === 'Digit4' || code === 'Digit5')) { 339 return true; 340 } 341 342 return false; 343 } 344 345 handleKeyDown(e) { 346 if (this.isScreenshotCombo(e)) { 347 this.addBlur('keycombo'); 348 // The capture is instantaneous; release this lock shortly after. 349 window.setTimeout(() => this.removeBlur('keycombo'), 1200); 350 e.preventDefault(); 351 return false; 352 } 353 354 // Pre-emptive shield: macOS swallows the digit keydown for screenshots, 355 // so blur as soon as the Cmd/Win+Shift modifier pair is held. 356 if (this.level.preemptiveModifier && e.shiftKey && (e.metaKey || e.ctrlKey)) { 357 this.lastModifierAt = Date.now(); 358 this.addBlur('modifier'); 359 } 360 361 // Block copy/save shortcuts. 362 if ((e.ctrlKey || e.metaKey) && (e.code === 'KeyC' || e.code === 'KeyS')) { 363 if (this.selectionTouchesProtectedImage() || e.code === 'KeyS') { 364 e.preventDefault(); 365 return false; 366 } 367 } 368 369 // Block print shortcut when print protection is on. 370 if (this.printProtection && (e.ctrlKey || e.metaKey) && e.code === 'KeyP') { 371 this.addBlur('print'); 372 e.preventDefault(); 373 return false; 374 } 375 } 376 377 handleKeyUp(e) { 378 if (this.neutralizeClipboard && (e.code === 'PrintScreen' || e.key === 'PrintScreen' || e.keyCode === 44)) { 379 this.neutralizeClipboardNow(); 380 } 381 382 // Release the pre-emptive modifier shield once modifiers are gone. 383 if (!e.shiftKey || (!e.metaKey && !e.ctrlKey)) { 384 this.removeBlur('modifier'); 385 } 386 387 if (this.printProtection && e.code === 'KeyP') { 388 window.setTimeout(() => this.removeBlur('print'), 500); 389 } 390 } 391 392 handleWindowBlur() { 393 // reCAPTCHA is rendered inside a cross-origin iframe. Clicking the 394 // checkbox or challenge moves focus into that iframe, which fires a 395 // window.blur on the parent page. Do not treat that as a screenshot. 396 if (document.activeElement && this.isRecaptchaElement(document.activeElement)) { 397 return; 398 } 399 400 // Snipping Tool / screenshot overlays / capture pickers steal window focus. 401 if (this.level.blurOnWindowBlur) { 402 this.addBlur('focus'); 403 return; 404 } 405 406 // Strict-only heuristic: focus lost right after a modifier press is very 407 // likely a screenshot tool, even in lower aggressiveness configs. 408 if (this.level.focusLossHeuristic && Date.now() - this.lastModifierAt < 400) { 409 this.addBlur('focus'); 410 } 411 } 412 413 handleWindowFocus() { 414 this.removeBlur('focus'); 415 } 416 417 handleVisibilityChange() { 418 if (document.hidden) { 419 this.addBlur('hidden'); 420 } else { 421 this.removeBlur('hidden'); 422 } 423 } 424 425 handleSelectStart(e) { 426 var target = e.target; 427 if (target && target.nodeType === Node.ELEMENT_NODE && target.closest && target.closest('.image-protection-on')) { 428 e.preventDefault(); 429 return false; 430 } 431 } 432 433 selectionTouchesProtectedImage() { 434 var selection = window.getSelection(); 435 if (!selection || selection.rangeCount === 0) { 436 return false; 437 } 438 439 for (var i = 0; i < this.filteredImages.length; i++) { 440 var img = this.filteredImages[i]; 441 if (img && selection.containsNode(img, true)) { 442 return true; 443 } 444 } 445 return false; 446 } 447 448 handleCopy(e) { 449 if (this.selectionTouchesProtectedImage()) { 450 e.preventDefault(); 451 return false; 452 } 453 } 454 455 neutralizeClipboardNow() { 456 try { 457 if (navigator.clipboard && navigator.clipboard.writeText) { 458 navigator.clipboard.writeText(' ').catch(() => {}); 459 } 460 } catch (err) { 461 // Best-effort only; ignore unsupported environments. 462 } 463 } 464 465 setupPeriodicClipboardClear() { 466 if (!this.neutralizeClipboard || !this.level.periodicClipboardClear) { 467 return; 468 } 469 470 this.clipboardInterval = window.setInterval(() => { 471 if (document.hasFocus()) { 472 this.neutralizeClipboardNow(); 473 } 474 }, 3000); 475 } 476 477 setupScreenRecordingGuard() { 478 if (!this.interceptScreenRecording) { 479 return; 480 } 481 if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) { 482 return; 483 } 484 485 var self = this; 486 var original = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices); 487 488 navigator.mediaDevices.getDisplayMedia = function() { 489 self.addBlur('recording'); 490
491 return original.apply(this, arguments).then((stream) => { 492 var tracks = stream.getTracks(); 493 var remaining = tracks.length; 494 495 if (!remaining) { 496 self.removeBlur('recording'); 497 } 498 499 tracks.forEach((track) => { 500 track.addEventListener('ended', () => { 501 remaining -= 1; 502 if (remaining <= 0) { 503 self.removeBlur('recording'); 504 } 505 }); 506 }); 507 508 return stream; 509 }).catch((err) => { 510 self.removeBlur('recording'); 511 throw err; 512 }); 513 }; 514 } 515 516 hrefPointsToImage(href) { 517 if (!href) { 518 return false; 519 } 520 521 var path = href.split('#')[0].split('?')[0]; 522 return /\.(jpe?g|png|gif|webp|avif|bmp|tiff?)$/i.test(path); 523 } 524 525 shouldBlockDragOf(el) { 526 if (!el || el.nodeType !== Node.ELEMENT_NODE || this.isProtectionExcluded(el)) { 527 return false; 528 } 529 530 if (el.matches('[data-image-protection="true"], .force-image-protection')) { 531 return true; 532 } 533 534 if (el.tagName === 'IMG') { 535 if (!this.blurImages) { 536 return false; 537 } 538 var src = el.currentSrc || el.getAttribute('src') || ''; 539 return !/\.svg(?:$|[?#])/i.test(src); 540 } 541 542 if (el.tagName === 'VIDEO') { 543 return this.blurVideos; 544 } 545 546 return this.blurImages && this.bgImages.indexOf(el) !== -1; 547 } 548 549 linkNeedsDragProtection(link) { 550 if (!link || this.isProtectionExcluded(link)) { 551 return false; 552 } 553 554 if (this.shouldBlockDragOf(link)) { 555 return true; 556 } 557 558 var media = link.querySelectorAll('img, video'); 559 var hasProtected = false; 560 var hasExcluded = false; 561 562 for (var i = 0; i < media.length; i++) { 563 if (this.isProtectionExcluded(media[i])) { 564 hasExcluded = true; 565 } else if (this.shouldBlockDragOf(media[i])) { 566 hasProtected = true; 567 } 568 } 569 570 if (hasProtected) { 571 return true; 572 } 573 574 // An opted-out image stays draggable, including when its link points at a file. 575 if (hasExcluded) { 576 return false; 577 } 578 579 return this.blurImages && this.hrefPointsToImage(link.getAttribute('href')); 580 } 581 582 isProtectedDrag(target) { 583 if (!target || target.nodeType !== Node.ELEMENT_NODE) { 584 return false; 585 } 586 587 if (this.shouldBlockDragOf(target)) { 588 return true; 589 } 590 591 var link = target.closest('a[href]'); 592 return !!(link && this.linkNeedsDragProtection(link)); 593 } 594 595 handleDragStart(e) { 596 if (!this.isProtectedDrag(e.target)) { 597 return; 598 } 599 600 var link = e.target.closest ? e.target.closest('a[href]') : null; 601 if (link) { 602 link.setAttribute('draggable', 'false'); 603 } 604 605 if (e.dataTransfer) { 606 e.dataTransfer.effectAllowed = 'none'; 607 } 608 609 e.preventDefault(); 610 } 611 612 setupImageLinkProtection() { 613 document.querySelectorAll('a[href]').forEach((link) => { 614 if (link.dataset && link.dataset.imageProtectionLink === '1') { 615 return; 616 } 617 618 if (!this.linkNeedsDragProtection(link)) { 619 return; 620 } 621 622 if (link.dataset) { 623 link.dataset.imageProtectionLink = '1'; 624 } 625 626 // Anchors are draggable by default. With the image marked undraggable, 627 // Safari/Chrome drag the link instead and macOS saves it as a .webloc 628 // pointing at the full-size file. 629 link.setAttribute('draggable', 'false'); 630 link.style.setProperty('-webkit-user-drag', 'none'); 631 link.style.setProperty('user-drag', 'none'); 632 link.addEventListener('dragstart', (e) => e.preventDefault()); 633 link.addEventListener('contextmenu', (e) => e.preventDefault()); 634 }); 635 } 636 637 setupImageProtection() { 638 this.filteredImages.forEach((img) => { 639 if (!this.bgImages.includes(img)) { 640 this.addElementProtection(img); 641 } 642 }); 643 } 644 645 setupVideoProtection() { 646 this.filteredVideos.forEach((video) => { 647 this.addElementProtection(video); 648 }); 649 } 650 651 addElementProtection(el) { 652 if (el.dataset && el.dataset.imageProtectionBound === '1') { 653 return; 654 } 655 if (el.dataset) { 656 el.dataset.imageProtectionBound = '1'; 657 } 658 659 el.classList.add('image-protection-on');
660 // setProperty keeps inline styles galleries already set (max-width, etc.). 661 // Appending to cssText can glue onto a declaration that has no trailing semicolon. 662 [ 663 'user-select', 664 '-webkit-user-select', 665 '-webkit-touch-callout', 666 '-webkit-user-drag', 667 '-khtml-user-drag', 668 '-moz-user-drag', 669 '-o-user-drag', 670 'user-drag' 671 ].forEach((prop) => { 672 el.style.setProperty(prop, 'none'); 673 }); 674 el.setAttribute('draggable', 'false'); 675 el.addEventListener('contextmenu', (e) => e.preventDefault()); 676 el.addEventListener('dragstart', (e) => e.preventDefault()); 677 } 678 679 // --- Blur state machine -------------------------------------------- 680 681 addBlur(reason) { 682 this.blurLocks.add(reason); 683 this.refreshBlur(); 684 } 685 686 removeBlur(reason) { 687 this.blurLocks.delete(reason); 688 this.refreshBlur(); 689 } 690 691 refreshBlur() { 692 if (this.blurLocks.size > 0) { 693 this.applyBlur(); 694 } else { 695 this.clearBlur(); 696 } 697 } 698 699 syncPseudoClasses() { 700 var before = new Set(); 701 var after = new Set(); 702 703 this.pseudoImages.forEach((item) => { 704 if (item.pseudo === '::before') { 705 before.add(item.element); 706 } else { 707 after.add(item.element); 708 } 709 }); 710 711 document.querySelectorAll('.image-protection-pseudo-before').forEach((el) => { 712 if (!before.has(el)) { 713 el.classList.remove('image-protection-pseudo-before'); 714 } 715 }); 716 document.querySelectorAll('.image-protection-pseudo-after').forEach((el) => { 717 if (!after.has(el)) { 718 el.classList.remove('image-protection-pseudo-after'); 719 } 720 }); 721 722 before.forEach((el) => el.classList.add('image-protection-pseudo-before')); 723 after.forEach((el) => el.classList.add('image-protection-pseudo-after')); 724 } 725 726 applyPseudoBlur() { 727 var before = new Set(); 728 var after = new Set(); 729 730 this.pseudoImages.forEach((item) => { 731 if (item.pseudo === '::before') { 732 before.add(item.element); 733 } else { 734 after.add(item.element); 735 } 736 }); 737 738 document.querySelectorAll('.image-protection-blur-before').forEach((el) => { 739 if (!before.has(el)) { 740 el.classList.remove('image-protection-blur-before'); 741 } 742 }); 743 document.querySelectorAll('.image-protection-blur-after').forEach((el) => { 744 if (!after.has(el)) { 745 el.classList.remove('image-protection-blur-after'); 746 } 747 }); 748 749 before.forEach((el) => el.classList.add('image-protection-blur-before')); 750 after.forEach((el) => el.classList.add('image-protection-blur-after')); 751 } 752 753 clearPseudoBlur() { 754 document.querySelectorAll('.image-protection-blur-before, .image-protection-blur-after').forEach((el) => { 755 el.classList.remove('image-protection-blur-before', 'image-protection-blur-after'); 756 }); 757 } 758 759 applyBlur() { 760 var elements = [...this.filteredImages, ...this.filteredVideos]; 761 elements.forEach((element) => { 762 element.style.transition = 'none'; 763 element.style.filter = 'blur(' + this.blurAmount + 'px)'; 764 }); 765 this.applyPseudoBlur(); 766 this.showMessage(); 767 } 768 769 clearBlur() { 770 var elements = [...this.filteredImages, ...this.filteredVideos]; 771 elements.forEach((element) => { 772 element.style.transition = ''; 773 element.style.filter = 'none'; 774 }); 775 this.clearPseudoBlur(); 776 this.hideMessage(); 777 } 778 779 showMessage() { 780 if (!this.message || document.querySelector('.image-protection-message')) { 781 return; 782 } 783 784 if (this.filteredImages.length === 0 && this.filteredVideos.length === 0 && this.pseudoImages.length === 0) { 785 return; 786 } 787 788 var overlay = document.createElement('div'); 789 overlay.classList.add('image-protection-message'); 790 overlay.style.cssText = 'position:fixed;bottom:20px;left:20px;right:20px;max-width:500px;background-color:rgba(0,0,0,0.8);color:#fff;padding:10px;border-radius:5px;z-index:2147483647;'; 791 overlay.textContent = this.message; 792 793 if (this.showCredit) { 794 overlay.appendChild(document.createElement('br')); 795 overlay.appendChild(document.createElement('br')); 796 overlay.appendChild(document.createTextNode('Protected by ')); 797 798 var creditLink = document.createElement('a'); 799 creditLink.href = 'https://thewebkitchen.co.uk/'; 800 creditLink.target = '_blank'; 801 creditLink.rel = 'noopener noreferrer'; 802 creditLink.style.color = 'white'; 803 creditLink.textContent = 'The Web Kitchen'; 804 overlay.appendChild(creditLink); 805 } 806 807 (document.body || document.documentElement).appendChild(overlay); 808 } 809 810 hideMessage() {
811 var overlay = document.querySelector('.image-protection-message'); 812 if (overlay) { 813 overlay.remove(); 814 } 815 } 816 } 817 818 if (document.readyState === 'loading') { 819 document.addEventListener('DOMContentLoaded', () => new ImageProtection()); 820 } else { 821 new ImageProtection(); 822 } 823})();
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.