PageSourceSearch

https://ttmf-mortgages.com/wp-content/plugins/image-protection/protection.js?ver=1.2.5

js ttmf-mortgages.com collected 2026-10-07 02:47:08 UTC 23,778 bytes, 823 lines download raw bytes

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.