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 }); }); })();