Greasy Fork is available in English.

dA_zoomImage

better zoom for images in deviation-view

// ==UserScript==
// @name         dA_zoomImage
// @namespace    http://phi.pf-control.de/userscripts/dA_zoomImage
// @version      2.0
// @description  better zoom for images in deviation-view
// @author       Dediggefedde
// @match        https://www.deviantart.com/*
// @grant        GM.setValue
// @grant        GM.getValue
// ==/UserScript==

/* jshint esnext:true */

(function() {
	'use strict';

	//%%% globale variables

	//imgGear copied from inkscape "render gear", slightly adjusted
	let imgGear = '<svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 20.444057 20.232336" > <g transform="translate(-15.480352,-5.6695418)">  <g transform="matrix(0.26458333,0,0,0.26458333,25.702381,15.78571)"  style="fill:#000000">  <path  style="fill:#000000;stroke:#000000;stroke-width:1"  d="m 28.46196,-3.25861 4.23919,-0.48535 0.51123,0.00182 4.92206,1.5536 v 4.37708 l -4.92206,1.5536 -0.51123,0.00182 -4.23919,-0.48535 -1.40476,6.15466 4.02996,1.40204 0.45982,0.22345 3.76053,3.53535 -1.89914,3.94361 -5.1087,-0.73586 -0.4614,-0.22017 -3.60879,-2.2766 -3.93605,4.93565 3.02255,3.01173 0.31732,0.40083 1.8542,4.81687 -3.42214,2.72907 -4.2835,-2.87957 -0.32017,-0.39856 -2.26364,-3.61694 -5.68776,2.73908 1.41649,4.0249 0.11198,0.49883 -0.41938,5.14435 -4.26734,0.97399 -2.6099,-4.45294 -0.11554,-0.49801 -0.47013,-4.2409 h -6.31294 l -0.47013,4.2409 -0.11554,0.49801 -2.6099,4.45294 -4.26734,-0.97399 -0.41938,-5.14435 0.11198,-0.49883 1.41649,-4.0249 -5.68776,-2.73908 -2.26364,3.61694 -0.32017,0.39856 -4.2835,2.87957 -3.42214,-2.72907 1.8542,-4.81687 0.31732,-0.40083 3.02255,-3.01173 -3.93605,-4.93565 -3.60879,2.2766 -0.4614,0.22017 -5.1087,0.73586 -1.89914,-3.94361 3.76053,-3.53535 0.45982,-0.22345 4.02996,-1.40204 -1.40476,-6.15466 -4.23919,0.48535 -0.51123,-0.00182 -4.92206,-1.5536 v -4.37708 l 4.92206,-1.5536 0.51123,-0.00182 4.23919,0.48535 1.40476,-6.15466 -4.02996,-1.40204 -0.45982,-0.22345 -3.76053,-3.53535 1.89914,-3.94361 5.1087,0.73586 0.4614,0.22017 3.60879,2.2766 3.93605,-4.93565 -3.02255,-3.01173 -0.31732,-0.40083 -1.8542,-4.81687 3.42214,-2.72907 4.2835,2.87957 0.32017,0.39856 2.26364,3.61694 5.68776,-2.73908 -1.41649,-4.0249 -0.11198,-0.49883 0.41938,-5.14435 4.26734,-0.97399 2.6099,4.45294 0.11554,0.49801 0.47013,4.2409 h 6.31294 l 0.47013,-4.2409 0.11554,-0.49801 2.6099,-4.45294 4.26734,0.97399 0.41938,5.14435 -0.11198,0.49883 -1.41649,4.0249 5.68776,2.73908 2.26364,-3.61694 0.32017,-0.39856 4.2835,-2.87957 3.42214,2.72907 -1.8542,4.81687 -0.31732,0.40083 -3.02255,3.01173 3.93605,4.93565 3.60879,-2.2766 0.4614,-0.22017 5.1087,-0.73586 1.89914,3.94361 -3.76053,3.53535 -0.45982,0.22345 -4.02996,1.40204 z"  />  <circle  style="fill:#ffffff;stroke:#000000;stroke-width:1"  cx="0"  cy="0"  r="15" />  </g>  </g> </svg>';
	//zoom cursor self-drawn
	let zoomCursor = `url("data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Ctitle%3ELayer 1%3C/title%3E%3Cellipse stroke='%23000' fill='url(%23svg_17)' stroke-width='2' stroke-dasharray='null' stroke-opacity='null' cx='13.95456' cy='12.81821' id='svg_4' rx='6.31813' ry='6.31813'/%3E%3Cline fill='none' stroke='%23000' stroke-width='2' stroke-dasharray='null' stroke-opacity='null' fill-opacity='null' x1='18.54543' y1='17.59089' x2='25.77965' y2='24.82511' id='svg_6' stroke-linejoin='null' stroke-linecap='null'/%3E%3Cline transform='rotate(90 25.2126 8.452)' stroke-linecap='null' stroke-linejoin='null' id='svg_1' y2='10.98273' x2='25.21259' y1='5.92127' x1='25.21259' stroke-dasharray='null' stroke='%2300bf00' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_2' y2='10.98273' x2='25.21259' y1='5.92127' x1='25.21259' stroke-dasharray='null' stroke='%2300bf00' fill='none'/%3E%3C/g%3E%3Cdefs%3E%3CradialGradient r='2.07054' cy='0.01172' cx='0.47656' spreadMethod='pad' id='svg_17'%3E%3Cstop offset='0.00391' stop-opacity='0.95703' stop-color='%23dbf9f9'/%3E%3Cstop offset='1' stop-opacity='0.10938' stop-color='%23ff56aa'/%3E%3C/radialGradient%3E%3C/defs%3E%3C/svg%3E") 16 16, zoom-in`;

	//enums
	let zoomModes = { fit: 0, width: 1, height: 2, full: 3 } //viewing images (zoom button)
	let clickModes = { cont: 0, img: 1, preview: 2, rect: 3, navi: 4, close: 5, setting: 6, wide: 7, height: 8, real: 9, window: 10 }; //event delegation button click

	let prevImg, //preview image
			img, //original image object
			zoomCont = 0, //zoom image div container
			zoomImg, //actually zoomed image
			highRec //highlight rectangle
	;
	let diag; //floating dialog div
	let lastZoomT = 0, //scroll zoom debouncer by timer
			lastResize = 0; //window resize debouncer by timer

	// let prevImg.offsetWidth, prevImg.offsetHeight, zoomImg.offsetWidth, zoomImg.offsetHeight, window.innerWidth, window.innerHeight;
	let userSet = { //user settings, saved/loaded by GM.
			opacityPrev: 50, //percent, drag-preview image opacity
			opacityHigh: 50, //percent, highlight rectangle opacity
			marginPrev: 10, //percent, margin around preview image
			lastZoom: zoomModes.fit //last zoom mode clicked = auto when open zoom
	};

	//%%%helper functions

	//set CSS style from array
	function setCss(el, arr) {
			if (!el) return;
			for (let i in arr) {
					el.style[i] = arr[i];
			}
	}

	//simple collision check, (x,y) inside obj-rectangle
	function coordInObj(x, y, obj) {
			let cImgOff = { left: obj.getBoundingClientRect().left + window.scrollY, top: obj.getBoundingClientRect().top + window.scrollY };

			return (x >= cImgOff.left && x <= cImgOff.left + prevImg.offsetWidth &&
					y >= cImgOff.top && y <= cImgOff.top + prevImg.offsetHeight);
	}

	//%%% DOM manipulation

	//moves zoomed image to (tx,ty)
	function moveToXY(tx, ty) {

			//highlight rect size, window/zoom scaled to preview size,
			let highr = {
					w: window.innerWidth / zoomImg.offsetWidth * prevImg.offsetWidth,
					h: window.innerHeight / zoomImg.offsetHeight * prevImg.offsetHeight
			};

			//percentage size scaled to preview image
			let highRat = {
					w: highr.w / prevImg.offsetWidth / 2,
					h: highr.h / prevImg.offsetHeight / 2
			};

			//real xy-position of preview image
			let cImgOff = {
					left: prevImg.getBoundingClientRect().left + window.scrollY,
					top: prevImg.getBoundingClientRect().top + window.scrollY
			};

			//relative shift of image (rect & zoom-image)
			let rel = { x: 0, y: 0 }

			if (zoomImg.offsetWidth < window.innerWidth) { //vertical center image if all fits on screen
					rel.x = 0;
			} else {
					rel.x = (tx - cImgOff.left) / prevImg.offsetWidth; //relative offset to preview, 0 in center
					rel.x = rel.x > 1 - highRat.w ? 1 - highRat.w : rel.x < highRat.w ? highRat.w : rel.x; //limit highlight-rec fit
					rel.x -= 0.5;
					rel.x = rel.x > 0.5 ? 0.5 : rel.x < -0.5 ? -0.5 : rel.x; //limit [-0.5,0.5]
			}

			if (zoomImg.offsetHeight < window.innerHeight) { //horizontal center image if all fits on screen
					rel.y = 0;
			} else { //same as rel.x
					rel.y = (ty - cImgOff.top) / prevImg.offsetHeight;
					rel.y = rel.y > 1 - highRat.h ? 1 - highRat.h : rel.y < highRat.h ? highRat.h : rel.y;
					rel.y -= 0.5;
					rel.y = rel.y > 0.5 ? 0.5 : rel.y < -0.5 ? -0.5 : rel.y;
			}

			//relative shift scaled to zoom image px.
			let offZoom = {
					x: -rel.x * zoomImg.offsetWidth,
					y: -rel.y * zoomImg.offsetHeight
			};

			//fix for margin:auto
			if (zoomImg.offsetWidth > window.innerWidth) offZoom.x -= (zoomImg.offsetWidth - window.innerWidth) / 2;
			// if(zoomImg.offsetHeight>window.innerHeight)offY-=(zoomImg.offsetHeight-window.innerHeight)/2; //margin auto only confuses X

			//moving zoom image
			zoomImg.style.transform = "translate(" + offZoom.x + "px," + offZoom.y + "px)";

			//moving/resizing highlight rectangle
			setCss(highRec, {
					width: highr.w + "px",
					height: highr.h + "px",
					transform: "translate(" + rel.x * prevImg.offsetWidth + "px," + rel.y * prevImg.offsetHeight + "px)"
			});
	}

	//fast zoom mode application
	function fullViewZoom(mode = zoomModes.fit, updateSet = true) {
			let sty = "";
			switch (mode) {
					case zoomModes.fit:
							if ((img.offsetWidth / img.offsetHeight) > (window.innerWidth / window.innerHeight)) return fullViewZoom(zoomModes.width);
							else fullViewZoom(zoomModes.height);
							break;
					case zoomModes.width:
							sty = { width: "100%", height: "auto", transform: "" };
							break;
					case zoomModes.height:
							sty = { height: "100%", width: "auto", transform: "" };
							break;
					case zoomModes.full:
							sty = { height: "auto", width: "auto", transform: "" };
							break;
			}

			setCss(zoomImg, sty);

			if (updateSet) {
					userSet.lastZoom = mode;
					GM.setValue("settings", JSON.stringify(userSet));
			}

	}

	//resized preview image,triggered at start and resize-window
	function resizePreview() {
			setCss(prevImg, {
					inset: userSet.marginPrev + "%",
					width: "unset",
					height: "unset",
					"max-width": (100 - userSet.marginPrev * 2) + "%",
					"max-height": (100 - userSet.marginPrev * 2) + "%"
			});

			setCss(highRec, { opacity: userSet.opacityHigh });
	}
	function checkURL(step=0){
			let tmpImage,mtch,imgsrc;
			switch(step){
					default:
					case 0: //check original image for dimension, min 400x400, thumbs are 96*96px, browse uses 340px heightupdateZoom(img.src);
							updateZoom(img.src); //use original image as placeholder and basis

							tmpImage = new Image();
							tmpImage.onload = function() {
									if(this.height<400|this.width<400){ //too small
											checkURL(1);
									}
									//all fine!
							};
							tmpImage.onerror = function() { //something wrong
									checkURL(1);
							};
							tmpImage.src=img.src;
							break;
					case 1: //try to change img url to access download images
							mtch=img.src.match(/(^.*?)(\/..?\/(?:crop|fill|fit)\/.*?)?(\?token=.*)/);
							imgsrc= mtch[1]+mtch[3];

							tmpImage = new Image();
							tmpImage.onload = function() {//image has accessible original image
									updateZoom(imgsrc);
									//switch to original fullview image
							};
							tmpImage.onerror = function() { //mostly response "forbidden", if direct access not enabled
									checkURL(2);
							};
							tmpImage.src=imgsrc;
							break;
					case 2: //use default scaler 1200px in height and width
							imgsrc=img.src.replace(/\/(crop|fill|fit)\/.*?\//,"/fit/w_1200,h_1200/");

							tmpImage = new Image();
							tmpImage.onload = function() {
									updateZoom(imgsrc);
									//switch to 1200px fit image
							};
							tmpImage.onerror = function() {
									checkURL(3); //if original is smaller than 1200, requests to fill are "forbidden".
							};
							tmpImage.src=imgsrc;
							break;
					case 3: //use default scaler 800px in height and width
							imgsrc=img.src.replace(/\/(crop|fill|fit)\/.*?\//,"/fit/w_800,h_800/");

							tmpImage = new Image();
							tmpImage.onload = function() {
									updateZoom(imgsrc);
									//switch to 800px fit image
							};
							tmpImage.onerror = function() {
									//no high-R image, image should still be img.src
									console.log("fallback original resolution", imgsrc,img.src);
							};
							tmpImage.src=imgsrc;
							break;
			};

	}

	function updateZoom(imgsrc) {
			if(!img)return;
			zoomImg.src = imgsrc ;
			prevImg.src = imgsrc ;
			resizePreview(); //adapt resize image
			fullViewZoom(userSet.lastZoom); //default zoom
	}

	//adds all elements to DOM (initial)
	function addElements(){
			//zoom container
			if(zoomCont)return;
			zoomCont = document.createElement("div");
			zoomCont.id = "dA_zoomImage_zoomCont";
			zoomCont.setAttribute("clickMode", clickModes.cont);
			document.body.appendChild(zoomCont);

			//zoomed image
			zoomImg = document.createElement("img");
			zoomImg.src ="";//imgsrc;
			zoomImg.id = "dA_zoomImage_zoomImg";
			zoomImg.setAttribute("clickMode", clickModes.img);
			setCss(zoomImg, { width: window.innerWidth + "px", height: window.innerHeight + "px" });
			zoomCont.appendChild(zoomImg);

			//preview image
			prevImg = document.createElement("img");//img.cloneNode(true);
			prevImg.setAttribute("clickMode", clickModes.preview);
			prevImg.id = "dA_zoomImage_prevImg";
			zoomCont.appendChild(prevImg);

			//highlight rectangle
			highRec = document.createElement("div");
			highRec.setAttribute("clickMode", clickModes.rect);
			highRec.id = "dA_zoomImage_highR";
			zoomCont.appendChild(highRec);

			//button container
			let navi = document.createElement("div");
			navi.setAttribute("clickMode", clickModes.navi);
			navi.id = "dA_zoomImage_navigation";
			zoomCont.appendChild(navi);

			//button container buttons (close)
			let closebut = document.createElement("div");
			closebut.title = "Close Zoom";
			closebut.setAttribute("clickMode", clickModes.close);
			closebut.innerHTML = "╳";
			navi.appendChild(closebut);

			//button container buttons (settings)
			let but = document.createElement("div");
			but.title = "dragFullscreen Settings";
			but.setAttribute("clickMode", clickModes.setting);
			but.innerHTML = imgGear;
			navi.appendChild(but);

			//button container buttons (fit width)
			let widebut = document.createElement("div");
			widebut.title = "Zoom Width";
			widebut.setAttribute("clickMode", clickModes.wide);
			widebut.innerHTML = "↔";
			navi.appendChild(widebut);

			//button container buttons (fit height)
			let highbut = document.createElement("div");
			highbut.title = "Zoom Height";
			highbut.setAttribute("clickMode", clickModes.height);
			highbut.innerHTML = "↕";
			navi.appendChild(highbut);

			//button container buttons (fit window)
			let fitbut = document.createElement("div");
			fitbut.title = "Zoom Fit Window";
			fitbut.setAttribute("clickMode", clickModes.window);
			fitbut.innerHTML = "⭾";
			navi.appendChild(fitbut);

			//button container buttons (full size)
			let realbut = document.createElement("div");
			realbut.title = "Real size";
			realbut.setAttribute("clickMode", clickModes.real);
			realbut.innerHTML = "⤧";
			navi.appendChild(realbut);

			//settings dialog
			diag = document.createElement("div");
			diag.id = "dA_zoomImage_settings";
			diag.style.display = "none";
			diag.innerHTML = `<form style='display:grid;grid-template-columns:auto auto 30px;grid-gap: 5px;'>
									<label for="previewOpacity">Preview Opacity (%):</label>
									<input type="range" min="0" max="100" value=${userSet.opacityPrev} name="previewOpacitySlider">
									<input type="text" value="${userSet.opacityPrev}" name="previewOpacity">
									<label for="highOpacity">Highlight Opacity (%):</label>
									<input type="range" min="0" max="100" value=${userSet.opacityHigh} name="highOpacitySlider">
									<input type="text" value="${userSet.opacityHigh}" name="highOpacity">
									<label for="previewMargin">Preview Margin (%):</label>
									<input type="range" min="0" max="40" value=${userSet.marginPrev} name="previewMarginSlider">
									<input type="text" value="${userSet.marginPrev}" name="previewMargin">
							</form><div id="dA_zoomImage_settings_OK">Save</div>`.replace(/\s\s+/g, "");
			document.body.appendChild(diag);

			document.getElementsByName("previewOpacitySlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("previewOpacity")[0].value = this.value;
			});
			document.getElementsByName("highOpacitySlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("highOpacity")[0].value = this.value;
			});
			document.getElementsByName("previewMarginSlider")[0].addEventListener("input", function(ev) {
					document.getElementsByName("previewMargin")[0].value = this.value;
			});

			document.getElementsByName("previewOpacity")[0].addEventListener("input", function(ev) {
					if (this.value > 100) this.value = 100;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("previewOpacitySlider")[0].value = this.value;
			});

			document.getElementsByName("highOpacity")[0].addEventListener("input", function(ev) {
					if (this.value > 100) this.value = 100;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("highOpacitySlider")[0].value = this.value;
			});

			document.getElementsByName("previewMargin")[0].addEventListener("input", function(ev) {
					if (this.value > 40) this.value = 40;
					if (this.value < 0) this.value = 0;
					document.getElementsByName("previewMarginSlider")[0].value = this.value;
			});


			//OK settings dialog button (save settings, apply changes, hide dialog)
			var okbut = document.querySelector("#dA_zoomImage_settings_OK");
			okbut.addEventListener("click", function(ev) {
					ev.stopPropagation();
					ev.preventDefault();
					userSet.opacityPrev = document.querySelector("#dA_zoomImage_settings input[name=previewOpacity]").value;
					userSet.opacityHigh = document.querySelector("#dA_zoomImage_settings input[name=highOpacity]").value;
					userSet.marginPrev = document.querySelector("#dA_zoomImage_settings input[name=previewMargin]").value;

					userSet.opacityPrev = userSet.opacityPrev > 100 ? 100 : userSet.opacityPrev < 0 ? 0 : userSet.opacityPrev;
					userSet.opacityHigh = userSet.opacityHigh > 100 ? 100 : userSet.opacityHigh < 0 ? 0 : userSet.opacityHigh;
					userSet.marginPrev = userSet.marginPrev > 40 ? 40 : userSet.marginPrev < 0 ? 0 : userSet.marginPrev;

					diag.style.display = "none";

					document.querySelector("#dA_zoomImage_prevImg").style.opacity = userSet.opacityPrev + "%";
					document.querySelector("#dA_zoomImage_highR").style.opacity = userSet.opacityHigh + "%";
					resizePreview();

					GM.setValue("settings", JSON.stringify(userSet));
			});
			zoomCont.addEventListener("mouseup", function(ev) { //event delegation
					ev.preventDefault();
					switch (parseInt(ev.target.getAttribute("clickMode"))) {
							case clickModes.cont: //close zoom when clicking on free space
									//free space=outside zoom and preview-img
									if (coordInObj(ev.pageX, ev.pageY, prevImg) && coordInObj(ev.pageX, ev.pageY, zoomImg)) { //if inside, "mouseup" hide preview and highlight
											ev.stopPropagation();
											prevImg.style.visibility = 'hidden';
											highRec.style.visibility = 'hidden';
											break;
									}
									//no break, close window if free space ↓
									// falls through (eslint comment)
							case clickModes.close: //close zoom
									zoomCont.style.display = 'none';
									diag.style.display = "none";
									ev.stopPropagation();
									break;
							case clickModes.wide: //wide zoom mode button
									fullViewZoom(zoomModes.width);
									ev.stopPropagation();
									break;
							case clickModes.window: //window fit zoom mode button
									fullViewZoom(zoomModes.fit);
									ev.stopPropagation();
									break;
							case clickModes.height: //height zoom mode button
									fullViewZoom(zoomModes.height);
									ev.stopPropagation();
									break;
							case clickModes.real: //wide zoom mode button
									fullViewZoom(zoomModes.full);
									ev.stopPropagation();
									break;
							case clickModes.img: //basically mouseup, remove preview/highlight
							case clickModes.rect:
							case clickModes.preview:
									prevImg.style.visibility = 'hidden';
									highRec.style.visibility = 'hidden';
									ev.stopPropagation();
									break;
							case clickModes.setting:
									document.querySelector("#dA_zoomImage_settings input[name=previewOpacity]").value = userSet.opacityPrev;
									document.querySelector("#dA_zoomImage_settings input[name=highOpacity]").value = userSet.opacityHigh;
									document.querySelector("#dA_zoomImage_settings input[name=previewMargin]").value = userSet.marginPrev;
									diag.style.display = "block";
									ev.stopPropagation();
									break;
							case NaN: //no clickmode = propagate
									break;
							default: //unused clickmodes
									ev.stopPropagation();
									break;
					}
			});

			//zoom img at mouse position
			zoomCont.addEventListener('wheel', function(ev) {
					ev.preventDefault();
					ev.stopPropagation();

					if (Date.now() - lastZoomT < 20) return; //wheel at 50evt/s
					lastZoomT = Date.now();

					if (ev.deltaY < 0) { //first zoom,  then scroll. bit jitterish?
							zoomImg.style.width = zoomImg.clientWidth * 1.1 + "px";
							zoomImg.style.height = zoomImg.clientHeight * 1.1 + "px";
					} else if (ev.deltaY > 0) {
							zoomImg.style.width = zoomImg.clientWidth / 1.1 + "px";
							zoomImg.style.height = zoomImg.clientHeight / 1.1 + "px";
					}
					moveToXY(ev.pageX, ev.pageY);
			});

			//moveing cursor = moving image
			zoomCont.addEventListener("mousemove", function(ev) {
					ev.preventDefault();
					ev.stopPropagation();

					if (Date.now() - lastZoomT < 50) return; //move at 20evt/s debounce
					lastZoomT = Date.now();

					moveToXY(ev.pageX, ev.pageY);
			});

			//show preview/highrect on click/drag
			zoomCont.addEventListener("mousedown", function(ev) {
					ev.preventDefault();
					switch (parseInt(ev.target.getAttribute("clickMode"))) {
							case clickModes.img:
							case clickModes.rect:
							case clickModes.preview:
									prevImg.style.visibility = 'visible';
									highRec.style.visibility = 'visible';
									ev.stopPropagation();
									break;
							case NaN:
							default:
									break;
					}
			});

			//window resize = resize preview
			document.addEventListener("resize", function(ev) {
					if (Date.now() - lastResize < 100) return; //resize at 10evt/s debounce
					lastResize = Date.now();

					resizePreview();
			});
	}

	//watchdog function on interval 1s
	//dA browsing is js based, so DOM might change and watchdog reacts if elements are applicable.
	function addDragger(mutationList, observer) {
			//  document.querySelectorAll("img[src*=wix]")
			let imgs = document.querySelectorAll("img[src*=wix]:not([dA_zoomImage])");//document.querySelector("div[data-hook=art_stage] img:not([dA_zoomImage])"); //react on art_stage-divs, once per element
			imgs.forEach(el=>{
					if (el != null) {
							el.setAttribute("dA_zoomImage", 1);
							// el.setAttribute("draggable", "false");
							img = el;

							//show zoom window
							el.addEventListener("click", function(ev) {
									if (!el.hasAttribute("fetchpriority")&&!ev.shiftKey)return;

									ev.stopPropagation();
									ev.preventDefault();
									img=ev.target;
									checkURL();
									zoomCont.style.display = 'block';
									prevImg.style.visibility = 'hidden';
									highRec.style.visibility = 'hidden';
							});
					}
			});
			addElements();
			//add elements;
			//addZoomElements();
	}


	//load settings, add custom style,  execute script
	GM.getValue("settings", "").then((val) => {
			if (val == "") return;
			userSet = JSON.parse(val);
			if (!userSet.hasOwnProperty("marginPrev")) userSet.marginPrev = 10; //default, backwards compability to v1.1
			if (!userSet.hasOwnProperty("lastZoom")) userSet.lastZoom = zoomModes.fit; //default, backwards compability to v1.5
	}).finally(() => {
			let style = document.createElement('style');
			style.textContent = `
	#dA_zoomImage_zoomImg{position:absolute;z-index:90;inset:0;margin:auto;}
	#dA_zoomImage_prevImg{position:fixed;visibility:hidden;cursor:move;z-index:90;margin:auto;opacity:${userSet.opacityPrev}%;}
	#dA_zoomImage_highR{position:fixed;border:2px solid #475c4daa;box-shadow:0px 0px 10px white inset;visibility:hidden;box-sizing:border-box;z-index:91;margin:auto;inset:0;opacity:${userSet.opacityHigh}%;}
	#dA_zoomImage_navigation{position:fixed;top:0;right:0;z-index:99;opacity:0.2}
			#dA_zoomImage_navigation:hover{opacity:1}
	#dA_zoomImage_navigation>div{cursor:pointer;background-color: var(--g-bg-tertiary);border-radius: 50px;
		line-height: 27px;text-align: center;width:30px;height:30px;margin:10px;padding:5px;font-size: larger;}
	#dA_zoomImage_navigation>div * {pointer-events: none;}
	#dA_zoomImage_zoomCont{position: fixed;z-index: 99;top: 0;left: 0;right: 0;bottom: 0;background: var(--g-bg-primary);display:none;}
	#dA_zoomImage_settings {position: fixed;top: 0;left: 0;right: 0;bottom: 0;z-index: 99;width: 400px;height: 120px;margin: auto;background-color: var(--g-bg-primary);padding: 10px;border-radius: 10px;border: 5px ridge var(--typography-tertiary-light);
		box-shadow: 3px 3px 5px black;}
	#dA_zoomImage_settings_OK{position: absolute;right: 10px;bottom: 10px;color: var(--typography-primary);padding: 5px;border-radius: 5px;border: 1px solid var(--typography-primary);cursor: pointer;}
	#dA_zoomImage_settings_OK:hover{box-shadow: inset 0 0 6px var(--typography-primary);}
			.shift_pressed img[da_zoomimage]{cursor:${zoomCursor}!important}
			img[da_zoomimage][fetchpriority]{cursor:${zoomCursor}!important}
`.replace(/\s\s+/g, ""); //indentation removed from string
			document.head.appendChild(style);

			document.body.addEventListener("keydown",(ev)=>{if(ev.shiftKey)document.body.classList.add("shift_pressed");;});
			document.body.addEventListener("keyup",(ev)=>{if(!ev.shiftKey);document.body.classList.remove("shift_pressed");});

			//start watchdog, 1s interval.
			const observer = new MutationObserver(addDragger);
			observer.observe(document.body,{ childList: true, subtree: true });

	});



})();