Mouseover Popup Image Viewer

Shows images and videos behind links and thumbnails.

// ==UserScript==
// @name        Mouseover Popup Image Viewer
// @namespace   http://w9p.co/userscripts/
// @description Shows images and videos behind links and thumbnails.
// @version     2017.9.29
// @author      kuehlschrank
// @homepage    http://w9p.co/userscripts/mpiv/
// @icon        https://w9p.co/userscripts/mpiv/icon.png
// @include     http*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @grant       GM_openInTab
// @grant       GM_registerMenuCommand
// @grant       GM_setClipboard
// @connect-src *
// ==/UserScript==

'use strict';

var d = document, wn = window, hostname = location.hostname, trusted = ['greasyfork.org', 'w9p.co'], imgtab = d.images.length == 1 && d.images[0].parentNode == d.body && !d.links.length, cfg = loadCfg(), enabled = cfg.imgtab || !imgtab, _ = {}, hosts;

function loadCfg() {
	return fixCfg(GM_getValue('cfg'), true);
}

function fixCfg(s, save) {
	var cfg, def = {
		version: 5,
		delay: 500,
		start: 'auto',
		zoom: 'context',
		center: false,
		imgtab: false,
		close: true,
		preload: false,
		css: '',
		scales: [],
		hosts: '',
		scale: 1.5,
		xhr: true
	};
	try { cfg = JSON.parse(s); } catch(ex) {}
	if(typeof cfg != 'object' || !cfg) cfg = {}; else if(cfg.version == def.version) return cfg;
	for(var dp in def) {
		if(def.hasOwnProperty(dp) && typeof cfg[dp] != typeof def[dp]) cfg[dp] = def[dp];
	}
	if(cfg.version == 3 && cfg.scales[0] === 0) cfg.scales[0] = '0!';
	for(var cp in cfg) {
		if(!def.hasOwnProperty(cp)) delete cfg[cp];
	}
	cfg.version = def.version;
	if(save) saveCfg(cfg);
	return cfg;
}

function saveCfg(newCfg) {
	GM_setValue('cfg', JSON.stringify(cfg = newCfg));
}

function loadHosts() {
	var hosts = [
		{d:'startpage', r:/\boiu=(.+)/, s:'$1', follow:true},
		{r:/[\/\?=](https?.+?)(&|$)/, s:'$1', follow:true},
		{d:'4chan.org', e:'.is_catalog .thread a[href*="/thread/"], .catalog-thread a[href*="/thread/"]', q:'.op .fileText a', css:'#post-preview{display:none}'},
		{r:/500px\.com\/photo\//, q:'meta[property="og:image"]'},
		{r:/attachment\.php.+attachmentid/},
		{r:/abload\.de\/image/, q:'#image'},
		{d:'amazon.', r:/(https?:\/\/[\.a-z-]+amazon\.com\/images\/I\/.+?)\./, s:function(m) { var uh = d.getElementById('universal-hover'); if(uh) return ''; return m[1] + '.jpg'; }, css:'#zoomWindow{display:none!important;}'},
		{r:/(chronos\.to|coreimg\.net)\/t\/([0-9]+)\/([0-9]+)\/([a-z0-9]+)/, s:'http://i$2.$1/i/$3/$4.jpg'},
		{r:/de?pic\.me\/[0-9a-z]{8,}/, q:'#pic'},
		{r:/deviantart\.com\/art\//, s:function(m, node) { return /\b(film|lit)/.test(node.className) || /in Flash/.test(node.title) ? '' : m.input; }, q:['#download-button[href*=".jpg"], #download-button[href*=".gif"], #download-button[href*=".png"], #gmi-ResViewSizer_fullimg', 'img.dev-content-full']},
		{r:/disqus\.com/, s:''},
		{r:/dropbox\.com\/sh?\/.+\.(jpe?g|gif|png)/i, q:function(text, doc) { var i = qs('img.absolute-center', doc); return i ? i.src.replace(/(size_mode)=\d+/, '$1=5') : false; }},
		{d:'dropbox.com', r:/(.+?&size_mode)=\d+(.*)/, s:'$1=5$2'},
		{r:/ebay\.[^\/]+\/itm\//, q:function(text) { return text.match(/https?:\/\/i\.ebayimg\.com\/[^\.]+\.JPG/i)[0].replace(/~~60_\d+/, '~~60_57'); }},
		{r:/i.ebayimg.com/, s:function(m, node) { if(qs('.zoom_trigger_mask', node.parentNode)) return ''; return m.input.replace(/~~60_\d+/, '~~60_57'); }},
		{r:/fastpic\.ru\/view\//, q:'#picContainer img'},
		{d:'facebook.com', e:'a[href*="ref=hovercard"]', s:function(m, node) { return 'https://www.facebook.com/photo.php?fbid=' + /\/[0-9]+_([0-9]+)_/.exec(qs('img', node).src)[1]; }, follow:true},
		{d:'facebook.com', r:/(fbcdn|fbexternal).*?(app_full_proxy|safe_image).+?(src|url)=(http.+?)[&\"']/, s:function(m, node) { return contains(node.parentNode.className, 'video') && contains(m[4], 'fbcdn') ? '' : decodeURIComponent(m[4]); }, html:true, follow:true},
		{r:/facebook\.com\/(photo\.php|[^\/]+\/photos\/)/, s:function(m, node) { if(node.id == 'fbPhotoImage') return false; if(/gradient\.png$/.test(m.input)) return ''; return m.input.replace('www.facebook.com', 'mbasic.facebook.com'); }, q:'div + span > a:first-child:not([href*="tag_faces"]), div + span > a[href*="tag_faces"] ~ a', rect:'#fbProfileCover'},
		{r:/fbcdn.+?[0-9]+_([0-9]+)_[0-9]+_[a-z]\.(jpg|png)/, s:function(m) { try { if(/[\.^]facebook\.com$/.test(hostname)) return unsafeWindow.PhotoSnowlift.getInstance().stream.cache.image[m[1]].url; } catch(ex) {} return false; }, manual:true},
		{r:/(https?:\/\/(fbcdn-[\w\.\-]+akamaihd|[\w\.\-]+?fbcdn)\.net\/[\w\/\.\-]+?)_[a-z]\.(jpg|png)(\?[0-9a-zA-Z0-9=_&]+)?/, s:function(m, node) { if(node.id == 'fbPhotoImage') { var a = qs('a.fbPhotosPhotoActionsItem[href$="dl=1"]', d.body); if(a) { return contains(a.href, m.input.match(/[0-9]+_[0-9]+_[0-9]+/)[0]) ? '' : a.href; } } if(m[4]) return false; if(contains(node.parentNode.outerHTML, '/hovercard/')) return ''; var gp = node.parentNode.parentNode; if(contains(node.outerHTML, 'profile') && contains(gp.href, '/photo')) return false; return m[1].replace(/\/[spc][\d\.x]+/g, '').replace('/v/', '/') + '_n.' + m[3]; }, rect:'.photoWrap'},
		{r:/firepic\.org\/\?v=/, q:'.well img[src*="firepic.org"]'},
		{r:/flickr\.com\/photos\/([0-9]+@N[0-9]+|[a-z0-9_\-]+)\/([0-9]+)/, s:function(m) { return m.input.indexOf('/sizes/') < 0 ? 'https://www.flickr.com/photos/' + m[1] + '/' + m[2] + '/sizes/sq/' : false; }, q:function(text, doc) { var links = qsa('.sizes-list a', doc); return 'https://www.flickr.com' + links[links.length-1].getAttribute('href'); }, follow:true},
		{r:/flickr\.com\/photos\/.+\/sizes\//, q:'#allsizes-photo > img'},
		{r:/gallery(nova|sense)\.se\/site\/v\//, q:'a[href*="/upload/"]'},
		{r:/gifbin\.com\/.+\.gif$/, xhr:true},
		{r:/(gfycat\.com\/)(gifs\/detail\/|iframe\/)?([a-z]+)/i, s:'https://$1$3', q:['meta[content$=".webm"]', '#webmsource', 'source[src$=".webm"]']},
		{r:/googleusercontent\.com\/(proxy|gadgets\/proxy.+?(http.+?)&)/, s:function(m) { return m[2] ? decodeURIComponent(m[2]) : m.input.replace(/w\d+-h\d+($|-p)/, 'w0-h0'); }},
		{r:/(googleusercontent|ggpht)\.com\//, s:function(m, node) { if(contains(m.input, 'webcache.') || node.outerHTML.match(/favicons\?|\b(Ol Rf Ep|Ol Zb ag|Zb HPb|Zb Gtb|Rf Pg|ho PQc|Uk wi hE|go wi Wh|we D0b|Bea)\b/) || matches(node, '.g-hovercard *, a[href*="profile_redirector"] > img')) return ''; return m.input.replace(/\/s\d{2,}-[^\/]+|\/w\d+-h\d+/, '/s0').replace(/=[^\/]+$/, ''); }},
		{r:/heberger-image\.fr\/images/, q:'#myimg'},
		{r:/hostingkartinok\.com\/show-image\.php.*/, q:'.image img'},
		{r:/imagearn\.com\/image/, q:'#img', xhr:true},
		{r:/imagefap\.com\/(image|photo)/, q:function(text, doc) { return qs('*[itemprop="contentUrl"]', doc).textContent; }},
		{r:/imagebam\.com\/image\//, q:'img[id]', tabfix:true, xhr:contains(hostname, 'planetsuzy')},
		{r:/imageban\.(ru|net)\/show|imgnova\.com|cweb-pix\.com|(imagebunk|imagewaste)\.com\/(image|pictures\/[0-9]+)/, q:'#img_obj', xhr:true},
		{r:/(imagepdb\.com|imgsure\.com|www\.pixoverflow\.com|imgwiki\.org|freeimgup\.com\/xxx)\/\?v=([0-9]+$|.+(?=\.[a-z]+))/, s:'http://$1/images/$2.jpg', xhr:true},
		{r:/img(\d+)\.(imageshack\.us)\/img\\1\/\d+\/(.+?)\.th(.+)$/, s:'https://$2/download/$1/$3$4'},
		{r:/imageshack\.us\/i\//, q:'#share-dl'},
		{r:/imageshost\.ru\/photo\//i, q:'#bphoto'},
		{r:/imageteam\.org\/img/, q:'img[alt="image"]'},
		{r:/(imagetwist\.com|imageshimage\.com|imgflare\.com|imgearn\.net)\/[a-z0-9]{8,}/, q:'img.pic', xhr:true},
		{r:/imageupper\.com\/i\//, q:'#img', xhr:true},
		{r:/imagepix\.org\/image\/(.+)\.html$/, s:'http://imagepix.org/full/$1.jpg', xhr:true},
		{r:/imageporter\.com\/i\//, s:'/_t//', xhr:true},
		{r:/imagevenue\.com\/img\.php/, q:'#thepic'},
		{r:/imagezilla\.net\/show\//, q:'#photo', xhr:true},
		{r:/(images-na\.ssl-images-amazon.com|media-imdb\.com)\/images\/.+?\.jpg/, s:'/V1\\.?_.+?\\.//g', distinct:true},
		{r:/imgbox\.com\/([a-z0-9]+)$/i, q:'#img', xhr:hostname != 'imgbox.com'},
		{r:/imgchili\.(net|com)\/show/, q:'#show_image', xhr:true},
		{r:/(hosturimage\.com|imageontime\.org|imggoo\.com|imgwel\.com|imageboom\.net|imageon\.org|img4ever\.net|imgcandy\.net|imgcredit\.xyz|imgdevil\.com|imgrun\.net|imgtrial\.com|imgult\.com|img\.yt|picspornfree\.me|pixliv\.com|pixxx\.me|uplimg\.com|xxxscreens\.com|xxxupload\.org)\/img-|imgbb\.net\/v-/, s:function(m) { return m.input.replace(/\/(v-[0-9a-f]+)_.+/, '$1').replace('http://img.yt', 'https://img.yt'); }, q:['img.centred_resized, #image', 'img[src*="/upload/big/"]'], xhr:true, post:'imgContinue=Continue%20to%20image%20...%20'},
		{r:/(foxyimg\.link|imgclick\.net|imgdragon\.com|imgmaid\.net|imgpaying\.com|imageeer\.com|imgdiamond\.com|imgmega\.com|imgsee\.me|imgtrex\.com|imgtiger\.org|pic-maniac\.com|picexposed\.com)\/([a-z0-9]+)/,  q:'img.pic', xhr:true, post:function(m) { return 'op=view&id=' + m[2] + '&pre=1&submit=Continue%20to%20image...'; }},
		{r:/imgflip\.com\/(i|gif)\/([^\/?#]+)/, s:function(m) { return 'https://i.imgflip.com/' + m[2] + (m[1] == 'i' ? '.jpg' : '.mp4'); }},
		{r:/imgsen\.se\/upload\//, s:'/small/big/', xhr:!true},
		{r:/imgtheif\.com\/image\//, q:'a > img[src*="/pictures/"]'},
		{r:/imgur\.com\/(a|gallery|t\/[a-z0-9_-]+)\/([a-z0-9]+)(#[a-z0-9]+)?/i, s:function(m) { return 'https://imgur.com/' + m[1] + '/' + m[2] + '' + (m[3] || ''); }, g:function(text, url, cb) { var mk = function(o, imgs) { var items = []; if(!o || !imgs) return items; for(var i = 0, len = imgs.length, cur; i < len && (cur = imgs[i]); i++) { var iu = 'https://i.imgur.com/' + cur.hash + cur.ext; if(cur.ext == '.gif' && !(cur.animated === false)) iu = [iu.replace('.gif', '.webm'), iu.replace('.gif', '.mp4'), iu]; items.push({url:iu, desc:cur.title && cur.description ? cur.title + ' - ' + cur.description : (cur.title || cur.description)}); } if(o.is_album && !contains(items[0].desc, o.title)) items.title = o.title; return items; }, m = /(mergeConfig\('gallery',\s*|Imgur\.Album\.getInstance\()(\{[\s\S]+?\})\);/.exec(text), o1 = eval('(' + m[2].replace(/analytics\s*:\s*analytics/, 'analytics:null').replace(/decodeURIComponent\(.+?\)/, 'null') + ')'), o = o1.image || o1.album, imgs = o.is_album ? o.album_images.images : [o]; if(!o.num_images || o.num_images <= imgs.length) return mk(o, imgs); GM_xmlhttpRequest({method:'GET',url:'https://imgur.com/ajaxalbums/getimages/' + o.hash + '/hit.json?all=true',onload:function(res) { var imgs; try { imgs = JSON.parse(res.responseText).data.images; } catch(ex) {} cb(mk(o, imgs)); }}); }, css:'.post > .hover { display:none!important; }'},
		{r:/imgur\.com\/.+,/i, g:function(text, url) { var hn = /([a-z]{2,}\.)?imgur\.com/.exec(url)[0]; return /.+\/([a-z0-9,]+)/i.exec(url)[1].split(',').map(function(id) { return {url:'https://i.' + hn + '/' + id + '.jpg'}; }); }},
		{r:/([a-z]{2,}\.)?imgur\.com\/(r\/[a-z]+\/|[a-z0-9]+#)?([a-z0-9]{5,})($|\?|\.([a-z]+))/i, s:function(m, node) { if(/memegen|random|register|search|signin/.test(m.input)) return ''; if(/(i\.([a-z]+\.)?)?imgur\.com\/(a\/|gallery\/)?/.test(node.parentNode.href || node.parentNode.parentNode.href)) return false; var url = 'https://i.' + (m[1] || '').replace('www.', '') + 'imgur.com/' + m[3].replace(/(.{7})[bhm]$/, '$1') + '.' + (m[5] ? m[5].replace(/gifv?/, 'webm') : 'jpg'); return contains(url, '.webm') ? [url, url.replace('.webm', '.mp4'), url.replace('.webm', '.gif')] : url; }},
		{d:'instagram.com', e:['a[href*="/p/"]', 'a[role="button"][data-reactid*="scontent-"]', 'article div', 'article div div img'], s:function(m, node) { var n = closest(node, 'a[href*="/p/"], article'); if(!n) return false; var a = matches(n, 'a[href*="/p/"]') ? n : qs('a[href*="/p/"]', n); return a.href; }, follow:true},
	    {r:/instagr(\.am|am\.com)\/p\//i, s:function(m) { return m.input.substr(0, m.input.lastIndexOf('/')) + '/?__a=1'; }, q:function(text) { var m = JSON.parse(text).graphql.shortcode_media;return m.video_url || m.display_url.replace(/\/[sp]\d+x\d+\//, '/').replace(/\?.+/, ''); }, rect:'div.PhotoGridMediaItem', c:function(text) { var m = JSON.parse(text).graphql.shortcode_media.edge_media_to_caption.edges[0]; if ( m === undefined ) { return "(no caption)"; } return m.node.text; } },
		{r:/(istoreimg\.com\/i|itmages\.ru\/image\/view)\//, q:'#image'},
		{d:'kat.cr', r:/confirm\/url\/([^\/]+)/, s:function(m) { return wn.atob(decodeURIComponent(m[1])); }, follow:true},
		{r:/(lazygirls\.info\/.+_.+?\/[a-z0-9_]+)($|\?)/i, s:'http://www.$1?display=fullsize', q:'img.photo', xhr:hostname != 'www.lazygirls.info'},
		{r:/ld-host\.de\/show/, q:'#image'},
		{r:/(listal|lisimg)\.com\/(view)?image\/([0-9]+)/, s:'http://iv1.lisimg.com/image/$3/0full.jpg'},
		{r:/(livememe\.com|lvme\.me)\/([^\.]+)$/, s:'http://i.lvme.me/$2.jpg'},
		{r:/lostpic\.net\/\?(photo|view)/, q:['#cool > img', '.casem img']},
		{r:/makeameme\.org\/meme\/([^\/?#]+)/, s:'https://media.makeameme.org/created/$1.jpg'},
		{r:/modelmayhem\.com\/photos\//, s:'/_m//'},
		{r:/modelmayhem\.com\/avatars\//, s:'/_t/_m/'},
		{r:/(min\.us|minus\.com)\/(i\/|l)([a-z0-9]+)$/i, s:'https://i.minus.com/i$3.jpg'},
		{r:/(min\.us|minus\.com)\/m[a-z0-9]+$/i, g:function(text) { var m = /gallerydata = ({[\w\W]+?});/.exec(text), o = JSON.parse(m[1]), items = []; items.title = o.name; for(var i = 0, len = o.items.length, cur; i < len && (cur = o.items[i]); i++) { items.push({url:'https://i.minus.com/i' + cur.id + '.jpg', desc:cur.caption}); } return items; }},
		{r:/(panoramio\.com\/.*?photo(\/|_id=)|google\.com\/mw-panoramio\/photos\/[a-z]+\/)(\d+)/, s:'http://static.panoramio.com/photos/original/$3.jpg'},
		{r:/(\d+\.photobucket\.com\/.+\/)(\?[a-z=&]+=)?(.+\.(jpe?g|png|gif))/, s:'http://i$1$3', xhr:!contains(hostname, 'photobucket.com')},
		{r:/(photosex\.biz|posteram\.ru)\/.+?id=/i, q:'img[src*="/pic_b/"]', xhr:true},
		{r:/pic4all\.eu\/(images\/|view\.php\?filename=)(.+)/, s:'http://$1/images/$3'},
		{r:/piccy\.info\/view3\/(.*)\//, s:'http://piccy.info/view3/$1/orig/', q:'#mainim'},
		{r:/picsee\.net\/([\d\-]+)\/(.+?)\.html/,s:'http://picsee.net/upload/$1/$2'},
		{r:/picturescream\.com\/\?v=/, q:'#imagen img'},
		{r:/(picturescream\.[a-z\/]+|imagescream\.com\/img)\/(soft|x)/, q:'a > img[src*="/images/"]'},
		{r:/pimpandhost\.com\/image\/([0-9]+)/, s:'http://pimpandhost.com/image/$1?size=original', q:'img.original'},
		{r:/pixhost\.org\/show\//, q:'#image', xhr:true},
		{r:/pixhub\.eu\/images/, q:'.image-show img', xhr:true},
		{r:/(pixroute|imgspice)\.com\/.+\.html$/, q:'img[id]', xhr:true},
		{r:/(pixsor\.com|euro-pic\.eu)\/share-([a-z0-9_]+)/i, s:'http://www.$1/image.php?id=$2', xhr:true},
		{r:/postima?ge?\.org\/image\/\w+/, q:['a[href*="dl="]', '#main-image']},
		{r:/radikal\.ru\/(fp|.+\.html)/, q:function(text) { return text.match(/http:\/\/[a-z0-9]+\.radikal\.ru[a-z0-9\/]+\.(jpg|gif|png)/i)[0]; }},
		{d:'reddit.com', r:/i\.reddituploads\.com/},
		{r:/screenlist\.ru\/details/, q:'#picture'},
		{r:/sharenxs\.com\/.+original$/, q:'img.view_photo', xhr:true},
		{r:/sharenxs\.com\/(gallery|view)\//, q:'a[href$="original"]', follow:true},
		{r:/stooorage\.com\/show\//, q:'#page_body div div img', xhr:true},
		{r:/((awsmpic|damimage|imagedecode|imghit|ocaload|swoopic)\.com|(imgflash|imgproof|imgserve|imgget)\.net|(dragimage|gogoimage|imgspot|imgstudio|madimage)\.org|imgs\.it|image\.re)\/img-/, q:'img.centred_resized, img.centred', xhr:true},
		{r:/turboimagehost\.com\/p\//, q:'#imageid', xhr:true},
		{r:/twimg.+\/profile_images/i, s:'/_(reasonably_small|normal|bigger|\d+x\d+)\\././g'},
		{r:/([a-z0-9-]+\.twimg\.com\/media\/[a-z0-9_-]+\.(jpe?g|png|gif))/i, s:'https://$1:orig', rect:'div.tweet a.twitter-timeline-link, div.TwitterPhoto-media'},
		{d:'tumblr.com',  e:'div.photo_stage_img, div.photo_stage > canvas', s:function(m, node) { return /http[^"]+/.exec(node.style.cssText + node.getAttribute('data-img-src'))[0]; }, follow:true},
		{r:/tumblr\.com.+_500\.jpg/, s:['/_500/_1280/', '']},
		{r:/twimg\.com\/1\/proxy.+?t=(.+?)[&_]/i, s:function(m) { return wn.atob(m[1]).match(/http.+/); }},
		{r:/pic\.twitter\.com\/[a-z0-9]+/i, q:function(text) { return text.match(/https?:\/\/twitter\.com\/[^\/]+\/status\/\d+\/photo\/\d+/i)[0]; }, follow:true},
		{d:'tweetdeck.twitter.com', e:'a.media-item, a.js-media-image-link', s:function(m, node) { return /http[^\)]+/.exec(node.style.backgroundImage)[0]; }, follow:true},
		{r:/twitpic\.com(\/show\/[a-z]+)?\/([a-z0-9]+)($|#)/i, s:'https://twitpic.com/show/large/$2'},
		{r:/twitter\.com\/.+\/status\/.+\/photo\//, q:'.OldMedia img, .media img, video.animated-gif, .AdaptiveMedia-singlePhoto img, .AdaptiveMedia-halfWidthPhoto img, .AdaptiveMedia-twoThirdsWidthPhoto img, .AdaptiveMedia-threeQuartersWidthPhoto img', follow:function(url) { return !/\.mp4$/.test(url); }},
		{d:'twitter.com', e:'.grid-tweet > .media-overlay', s:function(m, node) { return node.previousElementSibling.src; }, follow:true},
		{r:/upix\.me\/files/, s:'/#//'},
		{r:/(vine|seenive)\.com?\/v\//, q:'video source, meta[property="twitter:player:stream"]'},
		{r:/(web\.stagr(\.am|am\.com)|websta\.me)\/p\//i, q:function(text, doc) { var node = findNode(['div.jp-jplayer', 'meta[property="og:image"]'], doc); return findFile(node, _.url).replace(/\/[sp]\d+x\d+\//, '/'); }, rect:'div.PhotoGridMediaItem', c:function(text, doc) { var s = qs('meta[name="description"]', doc).getAttribute('content'); return s.substr(0, s.lastIndexOf(' | ')); } },
		{r:/wiki.+\/(thumb|images)\/.+\.(jpe?g|gif|png|svg)\/(revision\/)?/i, s:'/\\/thumb(?=\\/)|\\/scale-to-width(-[a-z]+)?\\/[0-9]+|\\/revision\\/latest|\\/[^\\/]+$//g', xhr:!contains(hostname, 'wiki')},
		{r:/((xxxhost|tinypix)\.me|(xxxces|imgsin)\.com)\/viewer/, q:['.text_align_center > img', 'img[alt]'], xhr:true},
		{r:/(i[0-9]*\.ytimg\.com\/vi\/[^\/]+)/, s:'https://$1/0.jpg', rect:'.video-list-item'},
		{r:/\/\/([^\/]+)\/viewer\.php\?file=(.+)/, s:'http://$1/images/$2', xhr:true},
		{r:/\/albums.+\/thumb_[^\/]/, s:'/thumb_//'},
		{r:/\/\/[^\/]+[^\?:]+\.(jpe?g?|gif|png|svg|webm)($|\?)/i, distinct:true}
	];
	if(cfg.hosts) {
		var lines = cfg.hosts.split(/[\r\n]+/);
		for(var i = lines.length, s; i-- && (s = lines[i]);) {
			try {
				var h = JSON.parse(s);
				if(h.r) h.r = new RegExp(h.r, 'i');
				if(h.s && typeof h.s == 'string' && contains(h.s, 'return ')) h.s = new Function('m', 'node', h.s);
				if(h.q && typeof h.q == 'string' && contains(h.q, 'return ')) h.q = new Function('text', 'doc', 'node', h.q);
				if(contains(h.c, 'return ')) h.c = new Function('text', 'doc', 'node', h.c);
				hosts.splice(0, 0, h);
			} catch(ex) {
				handleError('Host rule invalid: ' + s);
			}
		}
	}
	return hosts.filter(function(h) { return !h.d || contains(hostname, h.d); });
}

function onMouseOver(e) {
	if(!enabled || e.shiftKey || _.zoom || !activate(e.target, e.ctrlKey)) return;
	updateMouse(e);
	if(e.ctrlKey) {
		startPopup();
	} else if(cfg.start == 'auto' && !_.manual) {
		if(cfg.preload) {
			_.preloadStart = Date.now();
			startPopup();
			setStatus('preloading', 'add');
		} else {
			_.timeout = wn.setTimeout(startPopup, cfg.delay);
		}
		if(cfg.preload) wn.setTimeout(function() { setStatus('preloading', 'remove'); }, cfg.delay);
	}
	else
		setStatus('ready');
}

function onMouseOut(e) {
	if(!e.relatedTarget && !e.shiftKey) deactivate();
}

function onMouseMove(e) {
	updateMouse(e);
	if(e.shiftKey) return (_.lazyUnload = true);
	if(!_.zoomed && !_.cr) return deactivate();
	if(_.zoom) {
		placePopup();
		var bx = _.view.width/6, by = _.view.height/6;
		setStatus('edge', _.cx < bx || _.cx > _.view.width - bx || _.cy < by || _.cy > _.view.height - by ? 'add' : 'remove');
	}
}

function onMouseDown(e) {
	if(e.which != 3 && !e.shiftKey) deactivate(true);
	else if(e.shiftKey && e.which == 1 && _.popup && _.popup.controls) _.controlled = _.zoomed = true;
}

function onMouseScroll(e) {
	var dir = (e.deltaY || -e.wheelDelta) > 0 ? 1 : -1;
	if(_.zoom) {
		drop(e);
		var idx = _.scales.indexOf(_.scale);
		idx -= dir;
		if(idx >= 0 && idx < _.scales.length) _.scale = _.scales[idx];
		if(idx == 0 && cfg.close) {
			if(!_.gItems || _.gItems.length < 2) return deactivate(true);
			_.zoom = false;
			showFileInfo();
		}
		if(_.zooming) _.popup.classList.add('mpiv-zooming');
		placePopup();
		updateTitle();
	} else if(_.gItems && _.gItems.length > 1 && _.popup) {
		drop(e);
		nextGalleryItem(dir);
	} else if(cfg.zoom == 'wheel' && dir < 0 && _.popup) {
		drop(e);
		toggleZoom();
	} else {
		deactivate();
	}
}

function onKeyDown(e) {
	if(e.keyCode == 16) {
		setStatus('shift', 'add');
		if(_.popup && 'controls' in _.popup) _.popup.controls = true;
	} else if(e.keyCode == 17 && (cfg.start != 'auto' || _.manual) && !_.popup) {
		startPopup();
	}
}

function onKeyUp(e) {
	switch(e.keyCode) {
		case 16:
			setStatus('shift', 'remove');
			if(_.popup.controls) _.popup.controls = false;
			if(_.controlled) return _.controlled = false;
			_.popup && (_.zoomed || !('cr' in _) || _.cr) ? toggleZoom() : deactivate(true);
			break;
		case 17:
			break;
		case 27:
			deactivate(true);
			break;
		case 39:
		case 74:
			drop(e);
			nextGalleryItem(1);
			break;
		case 37:
		case 75:
			drop(e);
			nextGalleryItem(-1);
			break;
		case 68:
			drop(e);
			var name = (_.iurl || _.popup.src).split('/').pop().replace(/[:#\?].*/, '');
			if(!contains(name, '.')) name += '.jpg';
			saveFile(_.popup.src, name, function() { setBar('Could not download ' + name + '.', 'error'); });
			break;
		case 84:
			_.lazyUnload = true;
			if(_.tabfix && !_.xhr && tag(_.popup) == 'IMG' && contains(navigator.userAgent, 'Gecko/'))
				GM_openInTab('data:text/html;,' + encodeURIComponent('<html><head><style>body{margin:0;padding:0;background:#222}.fit{overflow:hidden}.fit>img{max-width:100vw;max-height:100vh}body>img{margin:auto;position:absolute;left:0;right:0;top:0;bottom:0}</style></head><body class="fit"><img onclick="document.body.classList.toggle(\'fit\')" src="' + _.popup.src + '"></body></html>'));
			else
				GM_openInTab(_.popup.src);
			deactivate();
			break;
		default:
			deactivate(true);
	}

}

function saveFile(url, name, onError) {
	var save = function(url) {
		var a = ce('a');
		a.href = url;
		a.download = name;
		a.dispatchEvent(new MouseEvent('click'));
	};
	if(contains(['blob:', 'data:'], url.substr(0, 5))) return save(url);
	GM_xmlhttpRequest({
		method: 'GET',
		url: url,
		responseType: 'blob',
		onload: function(res) {
			try {
				var ou = wn.URL.createObjectURL(res.response);
				save(ou);
				wn.setTimeout(function() { wn.URL.revokeObjectURL(ou); }, 1000);
			} catch(ex) {
				onError(ex);
			}
		},
		onError: onError
	});
}

function onContext(e) {
	if(e.shiftKey) return;
	if(cfg.zoom == 'context' && _.popup && toggleZoom()) return drop(e);
	if((cfg.start == 'context' || (cfg.start == 'auto' && _.manual)) && !_.status && !_.popup) {
		startPopup();
		return drop(e);
	}
	wn.setTimeout(function() { deactivate(true); }, 50);
}

function onMessage(e) {
	if(!contains(trusted, e.origin.substr(e.origin.indexOf('//') + 2)) || typeof e.data != 'string' || e.data.indexOf('mpiv-rule ') !== 0) return;
	if(!qs('#mpiv-setup', d)) setup();
	var inp = qs('#mpiv-hosts input:first-of-type', d);
	inp.value = e.data.substr(10).trim();
	inp.dispatchEvent(new Event('input', {bubbles:true}));
	inp.parentNode.scrollTop = 0;
	inp.select();
}

function startPopup() {
	setStatus(false);
	_.g ? startGalleryPopup() : startSinglePopup(_.url);
}

function startSinglePopup(url) {
	setStatus('loading');
	delete _.iurl;
	if(_.follow && !_.q && !_.s) {
		return findRedirect(_.url, function(url) {
			var info = findInfo(url, _.node, true);
			if(!info || !info.url) throw "Couldn't follow redirection target: " + url;
			restartSinglePopup(info);
		});
	}
	if(!_.q || Array.isArray(_.urls)) {
		if(typeof _.c == 'function') {
			_.caption = _.c(d.documentElement.outerHTML, d, _.node);
		} else if(typeof _.c == 'string') {
			var cnode = findNode(_.c, d);
			_.caption = cnode ? findCaption(cnode) : '';
		}
		_.iurl = url;
		return _.xhr ? downloadImage(url, _.url) : setPopup(url);
	}
	parsePage(url, function(iurl, cap, url) {
		if(!iurl) throw 'File not found.';
		if(typeof cap != 'undefined') _.caption = cap;
		if(_.follow === true || typeof _.follow == 'function' && _.follow(iurl)) {
			var info = findInfo(iurl, _.node, true);
			if(!info || !info.url) throw "Couldn't follow URL: " + iurl;
			return restartSinglePopup(info);
		}
		_.iurl = iurl;
		if(_.xhr) downloadImage(iurl, url); else setPopup(iurl);
	});
}

function restartSinglePopup(info) {
	for(var prop in info) _[prop] = info[prop];
	startSinglePopup(_.url);
}

function startGalleryPopup() {
	setStatus('loading');
	var startUrl = _.url;
	downloadPage(_.url, function(text, url) {
		try {
			var cb = function(items) {
				if(!_.url || _.url != startUrl) return;
				_.gItems = items;
				if(_.gItems.length == 0) {
					_.gItems = false;
					throw 'empty';
				}
				_.gIndex = findGalleryPosition(_.url);
				wn.setTimeout(nextGalleryItem, 0);
			};
			var items = _.g(text, url, cb);
			if(typeof items != 'undefined') cb(items);
		} catch(ex) {
			handleError('Parsing error: ' + ex);
		}
	});
}

function findGalleryPosition(gUrl) {
	var dir = 0, sel = gUrl.split('#')[1];
	if(sel) {
		if(/^[0-9]+$/.test(sel)) {
			dir += parseInt(sel);
		} else {
			for(var i = _.gItems.length; i--;) {
				var url = _.gItems[i].url;
				if(Array.isArray(url)) url = url[0];
				var file = url.substr(url.lastIndexOf('/') + 1);
				if(contains(file, sel)) {
					dir += i;
					break;
				}
			}
		}
	}
	return dir;
}

function loadGalleryParser(g) {
	if(typeof g == 'function') return g;
	if(typeof g == 'string') return new Function('text', 'url', 'cb', g);
	return function(text, url) {
		var qE = g.entry, qC = g.caption, qI = g.image, qT = g.title, fix = (typeof g.fix == 'string' ? new Function('s', 'isURL', g.fix) : g.fix) || function(s) { return s.trim(); };
		var doc = createDoc(text), items = [], nodes = qsa(qE || qI, doc);
		if(!Array.isArray(qC)) qC = [qC];
		for(var i = 0, node, len = nodes.length; i < len && (node = nodes[i]); i++) {
			var item = {};
			try {
				item.url = fix(findFile(qE ? qs(qI, node) : node, url), true);
				item.desc = qC.reduce(function(prev, q) {
					var n = qs(q, node);
					if(!n) {
						[node.previousElementSibling, node.nextElementSibling].forEach(function(es) {
							if(es && matches(es, qE) === false) n = matches(es, q) ? es : qs(q, es);
						});
					}
					return n ? (prev ? prev + ' - ' : '') + fix(n.textContent) : prev;
				}, '');
			} catch(ex) {}
			if(item.url) items.push(item);
		}
		var title = qs(qT, doc);
		if(title) items.title = fix(title.getAttribute('content') || title.textContent);
		return items;
	};
}

function nextGalleryItem(dir) {
	if(dir > 0 && (_.gIndex += dir) >= _.gItems.length)
		_.gIndex = 0;
	else if(dir < 0 && (_.gIndex += dir) < 0)
		_.gIndex = _.gItems.length - 1;
	var item = _.gItems[_.gIndex];
	if(Array.isArray(item.url)) {
		_.urls =  item.url.slice(0);
		_.url = _.urls.shift();
	} else {
		delete _.urls;
		_.url = item.url;
	}
	setPopup(false);
	startSinglePopup(_.url);
	showFileInfo();
	preloadNextGalleryItem(dir);
}

function preloadNextGalleryItem(dir) {
	var idx = _.gIndex + dir;
	if(_.popup && idx >= 0 && idx < _.gItems.length) {
		var url = _.gItems[idx].url;
		if(Array.isArray(url)) url = url[0];
		on(_.popup, 'load', function() {
			var img = ce('img');
			img.src = url;
		});
	}
}

function activate(node, force) {
	if(node == _.popup || node == d.body || node == d.documentElement) return;
	var info = parseNode(node);
	if(!info || !info.url || info.node == _.node) return;
	if(info.distinct && !force) {
		var scale = findScale(info.url, info.node.parentNode);
		if(scale && scale < cfg.scale) return;
	}
	if(_.node) deactivate();
	_ = info;
	_.view = viewRect();
	if(cfg.css || _.css) _.style = addStyle((contains(cfg.css, '{') ? cfg.css : '#mpiv-popup {' + cfg.css + '}') + (_.css ? _.css : ''));
	_.zooming = contains(cfg.css, 'mpiv-zooming');
	[_.node.parentNode, _.node, _.node.firstElementChild].some(function(n) {
			if(n && n.title && n.title != n.textContent && !contains(d.title, n.title) && !/^http\S+$/.test(n.title)) {
				_.tooltip = {node:n, text:n.title};
				n.title = '';
				return true;
			}
		});
	on(d, 'mousemove', onMouseMove);
	on(d, 'mouseout', onMouseOut);
	on(d, 'mousedown', onMouseDown);
	on(d, 'contextmenu', onContext);
	on(d, 'keydown', onKeyDown);
	on(d, 'keyup', onKeyUp);
	on(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
	return true;
}

function deactivate(wait) {
	wn.clearTimeout(_.timeout);
	if(_.req) try { _.req.abort(); } catch(ex) {}
	if(_.tooltip) _.tooltip.node.title = _.tooltip.text;
	updateTitle(true);
	setStatus(false);
	setPopup(false);
	setBar(false);
	rm(_.style);
	_ = {};
	off(d, 'mousemove', onMouseMove);
	off(d, 'mouseout', onMouseOut);
	off(d, 'mousedown', onMouseDown);
	off(d, 'contextmenu', onContext);
	off(d, 'keydown', onKeyDown);
	off(d, 'keyup', onKeyUp);
	off(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
	if(wait) {
		enabled = false;
		wn.setTimeout(function() { enabled = true; }, 200);
	}
}

function parseNode(node) {
	var a, img, url, info;
	if(!hosts) { hosts = loadHosts(); GM_registerMenuCommand('Set up Mouseover Popup Image Viewer', setup); }
	if(tag(node) == 'A') {
		a = node;
	} else {
		if(tag(node) == 'IMG') {
			img = node;
			if(img.src.substr(0, 5) != 'data:') url = rel2abs(img.src, location.href);
		}
		info = findInfo(url, node);
		if(info) return info;
		a = tag(node.parentNode) == 'A' ? node.parentNode : (tag(node.parentNode.parentNode) == 'A' ? node.parentNode.parentNode : false);
	}
	if(a) {
		url = a.getAttribute('data-expanded-url') || a.getAttribute('data-full-url') || a.getAttribute('data-url') || a.href;
		if(url.length > 750 || url.substr(0, 5) == 'data:') url = false;
		else if(contains(url, '//t.co/')) url = 'http://' + a.textContent;
		info = findInfo(url, a);
		if(info) return info;
	}
	if(img) return {url:img.src, node:img, rect:rect(img), distinct:true};
}

function findInfo(url, node, noHtml, skipHost) {
	for(var i = 0, len = hosts.length, tn = tag(node), h, m, html, urls; i < len && (h = hosts[i]); i++) {
		if(h.e && !matches(node, h.e) || h == skipHost) continue;
		if(h.r) {
			if(h.html && !noHtml && (tn == 'A' || tn == 'IMG' || h.e)) {
				if(!html) html = node.outerHTML;
				m = h.r.exec(html);
			} else if(url) {
				m = h.r.exec(url);
			} else {
				m = null;
			}
		} else {
			m = url ? /.*/.exec(url) : [];
		}
		if(!m || tn == 'IMG' && !('s' in h)) continue;
		if('s' in h) {
			urls = (Array.isArray(h.s) ? h.s : [h.s]).map(function(s) { if(typeof s == 'string') return decodeURIComponent(replace(s, m)); if(typeof s == 'function') return s(m, node); return s; });
			if(h.q && urls.length > 1) { console.log('Rule discarded. Substitution arrays can\'t be combined with property q.'); continue; }
			if(Array.isArray(urls[0])) urls = urls[0];
			if(urls[0] === false) continue;
			urls = urls.map(function(u) { return u ? decodeURIComponent(u) : u; });
		} else {
			urls = [m.input];
		}
		if((h.follow === true || typeof h.follow == 'function' && h.follow(urls[0])) && !h.q && h.s) return findInfo(urls[0], node, false, h);
		var info = {
			node: node,
			url: urls.shift(),
			urls: urls.length ? urls : false,
			r: h.r,
			q: h.q,
			c: h.c,
			g: h.g ? loadGalleryParser(h.g) : h.g,
			xhr: cfg.xhr && h.xhr,
			tabfix: h.tabfix,
			post: typeof h.post == 'function' ? h.post(m) : h.post,
			follow: h.follow,
			css: h.css,
			manual: h.manual,
			distinct: h.distinct,
			rect: rect(node, h.rect)
		};
		if(contains(hostname, 'twitter.com') && !/(facebook|google|twimg|twitter)\.com\//.test(info.url) || hostname == 'github.com' && !/github/.test(info.url) || contains(hostname, 'facebook.com') && /\bimgur\.com/.test(info.url)) info.xhr = 'data';
		return info;
	}
}

function downloadPage(url, cb) {
	var req, opts = {
		method: 'GET',
		url: url,
		onload: function(res) {
			try {
				if(req != _.req) return;
				delete _.req;
				if(res.status > 399) throw 'Server error: ' + res.status;
				cb(res.responseText, res.finalUrl || url);
			} catch(ex) {
				handleError(ex);
			}
		},
		onerror: function(res) {
			if(req == _.req) handleError(res);
		}
	};
	if(_.post) {
		opts.method = 'POST';
		opts.data = _.post;
		opts.headers = {'Content-Type':'application/x-www-form-urlencoded','Referer':url};
	}
	_.req = req = GM_xmlhttpRequest(opts);
}

function downloadImage(url, referer) {
	var start = Date.now(), bar, req;
	_.req = req = GM_xmlhttpRequest({
		method: 'GET',
		url: url,
		overrideMimeType: 'text/plain; charset=x-user-defined',
		headers: {'Accept':'image/png,image/*;q=0.8,*/*;q=0.5','Referer':referer},
		onprogress: function(e) {
			if(req != _.req) return;
			if(!bar && Date.now() - start > 3000 && e.loaded/e.total < 0.5) bar = true;
			if(bar) setBar(parseInt(e.loaded/e.total * 100) + '% of ' + (e.total/1000000).toFixed(1) + ' MB', 'xhr');
		},
		onload: function(res) {
			try {
				if(req != _.req) return;
				delete _.req;
				setBar(false);
				if(res.status > 399) throw 'HTTP error ' + res.status;
				var txt = res.responseText, ui8 = new Uint8Array(txt.length), type;
				for(var i = txt.length; i--;) {
					ui8[i] = txt.charCodeAt(i);
				}
				if(/Content-Type:\s*(.+)/i.exec(res.responseHeaders) && !contains(RegExp.$1, 'text/plain')) type = RegExp.$1;
				if(!type) {
					var ext = /\.([a-z0-9]+?)($|\?|#)/i.exec(url) ? RegExp.$1.toLowerCase() : 'jpg', types = {bmp:'image/bmp', gif:'image/gif', jpe:'image/jpeg', jpeg:'image/jpeg', jpg:'image/jpeg', mp4:'video/mp4', png:'image/png', svg:'image/svg+xml', tif:'image/tiff', tiff:'image/tiff', webm:'video/webm'};
					type = ext in types ? types[ext] : 'application/octet-stream';
				}
				var b = new Blob([ui8.buffer], {type:type});
				if(wn.URL && _.xhr != 'data') return setPopup(wn.URL.createObjectURL(b));
				var fr = new FileReader();
				fr.onload = function() { setPopup(fr.result); };
				fr.onerror = handleError;
				fr.readAsDataURL(b);
			} catch(ex) {
				handleError(ex);
			}
		},
		onerror: function(res) {
			if(req == _.req) handleError(res);
		}
	});
}

function findRedirect(url, cb) {
	var req;
	_.req = req = GM_xmlhttpRequest({
		url: url,
		method: 'HEAD',
		headers: {Referer:location.href.replace(location.hash, '')},
		onload: function(res) {
			if(req == _.req) cb(res.finalUrl);
		}
	});
}

function parsePage(url, cb) {
	downloadPage(url, function(html, url) {
		var iurl, cap, doc = createDoc(html);
		if(typeof _.q == 'function') {
			iurl = _.q(html, doc, _.node);
			if(Array.isArray(iurl)) {
				_.urls = iurl.slice(0);
				iurl = _.urls.shift();
			}
		} else {
			var inode = findNode(_.q, doc);
			iurl = inode ? findFile(inode, url) : false;
		}
		if(typeof _.c == 'function') {
			cap = _.c(html, doc, _.node);
		} else if(typeof _.c == 'string') {
			var cnode = findNode(_.c, doc);
			cap = cnode ? findCaption(cnode) : '';
		}
		cb(iurl, cap, url);
	});
}

function findNode(q, doc) {
	var node;
	if(!q) return;
	if(!Array.isArray(q)) q = [q];
	for(var i = 0, len = q.length; i < len; i++) {
		node = qs(q[i], doc);
		if(node) break;
	}
	return node;
}

function findFile(n, url) {
	var base = qs('base[href]', n.ownerDocument);
	var path =  n.getAttribute('src') || n.getAttribute('data-m4v') || n.getAttribute('href') || n.getAttribute('content') || /https?:\/\/[.\/a-z0-9_+%\-]+\.(jpe?g|gif|png|svg|webm|mp4)/i.exec(n.outerHTML) && RegExp.lastMatch;
	return path ? rel2abs(path.trim(), base ? base.getAttribute('href') : url) : false;
}

function findCaption(n) {
	return n.getAttribute('content') || n.getAttribute('title') || n.textContent;
}

function checkProgress(start) {
	if(start === true) {
		if(checkProgress.interval) wn.clearInterval(checkProgress.interval);
		checkProgress.interval = wn.setInterval(checkProgress, 150);
		return;
	}
	var p = _.popup;
	if(!p) return wn.clearInterval(checkProgress.interval);
	if(!updateSize()) return;
	wn.clearInterval(checkProgress.interval);
	if(_.preloadStart) {
		var wait = _.preloadStart + cfg.delay - Date.now();
		if(wait > 0) return _.timeout = wn.setTimeout(checkProgress, wait);
	}
	if(_.urls && _.urls.length && Math.max(_.nheight, _.nwidth) < 130) return handleError({type:'error'});
	setStatus(false);
	p.clientHeight;
	p.className = 'mpiv-show';
	updateSpacing();
	updateScales();
	updateTitle();
	placePopup();
	if(!_.bar) showFileInfo();
	if(_.large = _.nwidth > p.clientWidth + _.mbw || _.nheight > p.clientHeight + _.mbh) setStatus('large');
	if(cfg.imgtab && imgtab || cfg.zoom == 'auto') toggleZoom();
}

function updateSize() {
	var p = _.popup;
	_.nheight = p.naturalHeight || p.videoHeight || p.loaded && 800;
	_.nwidth  = p.naturalWidth || p.videoWidth || p.loaded && 1200;
	return !!_.nheight;
}

function updateSpacing() {
	var s = wn.getComputedStyle(_.popup);
	_.pw = styleSum(s, ['padding-left', 'padding-right']);
	_.ph = styleSum(s, ['padding-top', 'padding-bottom']);
	_.mbw = styleSum(s, ['margin-left', 'margin-right', 'border-left-width', 'border-right-width']);
	_.mbh = styleSum(s, ['margin-top', 'margin-bottom', 'border-top-width', 'border-bottom-width']);
}

function updateScales() {
	var scales = cfg.scales.length ? cfg.scales : ['0!', 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5, 8, 16], fit = Math.min(( _.view.width - _.mbw)/_.nwidth, (_.view.height - _.mbh)/_.nheight), cutoff = _.scale = Math.min(1, fit);
	_.scales = [];
	for(var i = scales.length; i--;) {
		var val = parseFloat(scales[i]) || fit, opt = typeof scales[i] == 'string' ? scales[i].slice(-1) : 0;
		if(opt == '!') cutoff = val;
		if(opt == '*') _.zscale = val;
		if(val != _.scale) _.scales.push(val);
	}
	_.scales = _.scales.filter(function(x) { return x >= cutoff; });
	_.scales.sort(function(a, b) { return a - b; });
	_.scales.unshift(_.scale);
}

function updateMouse(e) {
	_.cx = e.clientX;
	_.cy = e.clientY;
	var r = _.rect;
	if(r) _.cr = _.cx < r.right + 2 && _.cx > r.left - 2 && _.cy < r.bottom + 2 && _.cy > r.top - 2;
}

function showFileInfo() {
	if(_.gItems) {
		var item = _.gItems[_.gIndex];
		var c = _.gItems.length > 1 ? '[' + (_.gIndex + 1) + '/' + _.gItems.length + '] ' : '';
		if(_.gIndex == 0 && _.gItems.title && (!item.desc || !contains(item.desc, _.gItems.title))) c += _.gItems.title + (item.desc ? ' - ' : '');
		if(item.desc) c += item.desc;
		if(c) setBar(c.trim(), 'gallery', true);
	} else if('caption' in _) {
		setBar(_.caption, 'caption');
	} else if(_.tooltip) {
		setBar(_.tooltip.text, 'tooltip');
	}
}

function updateTitle(reset) {
	if(reset) {
		if(typeof _.title == 'string') d.title = _.title;
	} else {
		if(typeof _.title != 'string') _.title = d.title;
		d.title = Math.round(_.scale * 100) + '% - ' + _.nwidth + 'x' + _.nheight;
	}
}

function placePopup() {
	var p = _.popup;
	if(!p) return;
	var x = null, y = null, w = Math.round(_.scale * _.nwidth), h = Math.round(_.scale * _.nheight), cx = _.cx, cy = _.cy, vw = _.view.width, vh = _.view.height;
	if(!_.zoom && (!_.gItems || _.gItems.length < 2) && !cfg.center) {
		var r = _.rect, rx = (r.left + r.right) / 2, ry = (r.top + r.bottom) / 2;
		if(vw - r.right - 40 > w + _.mbw || w + _.mbw < r.left - 40) {
			if(h + _.mbh < vh - 60) y = Math.min(Math.max(ry - h/2, 30), vh - h - 30);
			x = rx > vw/2 ? r.left - 40 - w : r.right + 40;
		} else if(vh - r.bottom - 40 > h + _.mbh || h + _.mbh < r.top - 40) {
			if(w + _.mbw < vw - 60) x = Math.min(Math.max(rx - w/2, 30), vw - w - 30);
			y = ry > vh/2 ? r.top - 40 - h : r.bottom + 40;
		}
	}
	if(x == null) x = Math.round((vw > w ? vw/2 - w/2 : -1 * Math.min(1, Math.max(0, 5/3*(cx/vw-0.2))) * (w - vw)) - (_.pw + _.mbw)/2);
	if(y == null) y = Math.round((vh > h ? vh/2 - h/2 : -1 * Math.min(1, Math.max(0, 5/3*(cy/vh-0.2))) * (h - vh)) - (_.ph + _.mbh)/2);
	p.style.cssText = 'width:' + w + 'px!important;height:' + h + 'px!important;left:' + x + 'px!important;top:' + y + 'px!important';
}

function toggleZoom() {
	var p = _.popup;
	if(!p || !_.scales || _.scales.length < 2) return;
	_.zoom = !_.zoom;
	_.zoomed = true;
	_.scale = _.scales[ _.zoom ? (_.scales.indexOf(_.zscale) > 0 ? _.scales.indexOf(_.zscale) : 1) : 0];
	if(_.zooming) p.classList.add('mpiv-zooming');
	placePopup();
	updateTitle();
	setStatus(_.zoom ? 'zoom' : false);
	if(cfg.zoom != 'auto') setBar(false);
	if(!_.zoom) showFileInfo();
	return _.zoom;
}

function handleError(o) {
	var m = [o.message || (o.readyState ? 'Request failed.' : (o.type == 'error' ? 'File can\'t be displayed.' + (qs('div[bgactive*="flashblock"]', d) ? ' Check Flashblock settings.' : '') : o))];
		try {
			if(o.stack) m.push(' @ ' + o.stack.replace(/<?@file:.+?\.js/g, ''));
			if(_.r) m.push('RegExp: ' + _.r);
			if(_.url) m.push('URL: ' + _.url);
			if(_.iurl) m.push('File: ' + _.iurl);
			console.log(m.join('\n'));
		} catch(ex) {}
	if(contains(hostname, 'google') && contains(location.search, 'tbm=isch') && !_.xhr && cfg.xhr) {
		_.xhr = true;
		startSinglePopup(_.url);
	} else if(_.urls && _.urls.length) {
		_.url = _.urls.shift();
		if(!_.url)
			deactivate();
		else
			startSinglePopup(_.url);
	} else if(_.node) {
		setStatus('error');
		setBar(m[0], 'error');
	}
}

function setStatus(status, flag) {
	var de = d.documentElement, cn = de.className;
	if(flag == 'remove') {
        cn = cn.replace('mpiv-' + status, '');
	} else {
		if(flag != 'add') cn = cn.replace(/mpiv-[a-z]+/g, '');
		if(status && !contains(cn, 'mpiv-' + status)) cn += ' mpiv-' + status;
	}
	de.className = cn;
}

function setPopup(src) {
	var p = _.popup;
	if(p) {
		_.zoom = false;
		off(p, 'error', handleError);
		if(typeof p.pause == 'function') p.pause();
		if(!_.lazyUnload) {
			if(p.src.substr(0, 5) == 'blob:') wn.URL.revokeObjectURL(p.src);
			p.src = '';
		}
		rm(p);
		delete _.popup;
	}
	if(!src) return;
	if(src.substr(0, 5) != 'data:' && /\.(webm|mp4)($|\?)/.test(src) || src.substr(0, 10) == 'data:video') {
		var start = Date.now(), bar;
		var onProgress = function(e) {
			var p = e.target;
			if(!p.duration || !p.buffered.length || Date.now() - start < 2000) return;
			var per = parseInt(p.buffered.end(0)/p.duration * 100);
			if(!bar && per > 0 && per < 50) bar = true;
			if(bar) setBar(per + '% of ' + Math.round(p.duration) + 's', 'xhr');
		};
		p = _.popup = ce('video');
		p.autoplay = true;
		p.loop = true;
		p.volume = 0.5;
		p.controls = false;
		on(p, 'progress', onProgress);
		on(p, 'canplaythrough', function(e) { off(e.target, 'progress', onProgress); if(_.bar && _.bar.classList.contains('mpiv-xhr')) { setBar(false); showFileInfo(); } });
	} else {
		p = _.popup = ce('img');
	}
	p.id = 'mpiv-popup';
	on(p, 'error', handleError);
	on(p, 'load', function() { this.loaded = true; });
	if(_.zooming) on(p, 'transitionend', function(e) { e.target.classList.remove('mpiv-zooming'); });
	_.bar ? d.body.insertBefore(p, _.bar) : d.body.appendChild(p);
	p.src = src;
	p = null;
	checkProgress(true);
}

function setBar(label, cn) {
	var b = _.bar;
	if(!label) {
		rm(b);
		delete _.bar;
		return;
	}
	if(!b) {
		b = _.bar = ce('div');
		b.id = 'mpiv-bar';
	}
	b.innerHTML = label;
	if(!b.parentNode) {
		d.body.appendChild(b);
		b.clientHeight;
	}
	b.className = 'mpiv-show mpiv-' + cn;
}

function rel2abs(rel, abs) {
	if(rel.substr(0, 5) == 'data:') return rel;
	var re = /^([a-z]+:)\/\//;
	if(re.test(rel))  return rel;
	if(!re.exec(abs)) return;
	if(rel.indexOf('//') === 0) return RegExp.$1 + rel;
	if(rel[0] == '/') return abs.substr(0, abs.indexOf('/', RegExp.lastMatch.length)) + rel;
	return abs.substr(0, abs.lastIndexOf('/')) + '/' + rel;
}

function replace(s, m) {
	if(!m) return s;
	if(s.charAt(0) == '/' && s.charAt(1) != '/') {
		var mid = /[^\\]\//.exec(s).index+1;
		var end = s.lastIndexOf('/');
		var re = new RegExp(s.substring(1, mid), s.substr(end+1));
		return m.input.replace(re, s.substring(mid+1, end));
	}
	for(var i = m.length; i--;) {
		s = s.replace('$'+i, m[i]);
	}
	return s;
}

function addStyle(css) {
	var s = ce('style');
	s.textContent = css;
	d.head.appendChild(s);
	return s;
}

function styleSum(s, p) {
	for(var i = p.length, x = 0; i--;) {
		x += parseInt(s.getPropertyValue([p[i]])) || 0;
	}
	return x;
}

function findScale(url, parent) {
	var imgs = qsa('img, video', parent);
	for(var i = imgs.length, img; i-- && (img = imgs[i]);) {
		if(img.src != url) continue;
		var s = Math.max((img.naturalHeight || img.videoHeight)/img.offsetHeight, (img.naturalWidth || img.videoWidth)/img.offsetWidth);
		if(isFinite(s)) return s;
	}
}

function viewRect() {
	var node = d.compatMode == 'BackCompat' ? d.body : d.documentElement;
	return {width:node.clientWidth, height:node.clientHeight};
}

function rect(node, q) {
	var n;
	if(q) {
		n = node;
		while(tag(n = n.parentNode) != 'BODY') {
			if(matches(n, q)) return n.getBoundingClientRect();
		}
	}
	var nodes = qsa('*', node);
	for(var i = nodes.length; i-- && (n = nodes[i]);) {
		if(n.offsetHeight > node.offsetHeight) node = n;
	}
	return node.getBoundingClientRect();
}

function matches(n, q) {
	var p = Element.prototype, m = p.matches || p.mozMatchesSelector || p.webkitMatchesSelector || p.oMatchesSelector;
	if(m) return m.call(n, q);
}

function closest(n, q) {
	while(n) {
		if(matches(n, q)) return n;
		n = n.parentNode;
	}
}

function tag(n) {
	return n.tagName.toUpperCase();
}

function createDoc(text) {
	var doc = d.implementation.createHTMLDocument('MPIV');
	doc.documentElement.innerHTML = text;
	return doc;
}

function rm(n) {
	if(n && n.parentNode) n.parentNode.removeChild(n);
}

function on(n, e, f) {
	n.addEventListener(e, f);
}

function off(n, e, f) {
	n.removeEventListener(e, f);
}

function drop(e) {
	e.preventDefault();
	e.stopPropagation();
}

function ce(s) {
	return d.createElement(s);
}

function qs(s, n) {
	return n.querySelector(s);
}

function qsa(s, n) {
	return n.querySelectorAll(s);
}

function contains(a, b) {
	return a && a.indexOf(b) > -1;
}

function setup() {
	var $ = function(s) { return d.getElementById('mpiv-'+s); };
	var close = function() { rm($('setup')); if(!contains(trusted, hostname)) off(wn, 'message', onMessage); };
	var update = function() { $('delay').parentNode.style.display = $('preload').parentNode.style.display = $('start-auto').selected ? '' : 'none';	};
	var check = function(e) {
		var t = e.target, ok;
		try {
			var pes = t.previousElementSibling;
			if(t.value) {
				if(!pes) { var inp = t.cloneNode(); inp.value = ''; t.parentNode.insertBefore(inp, t); }
				new RegExp(JSON.parse(t.value).r);
			} else if(pes) {
				pes.focus();
				rm(t);
			}
			ok = 1;
		} catch(ex) {}
		t.style.backgroundColor = ok ? '' : '#ffaaaa';
	};
	var exp = function(e) {
		drop(e);
		var s = JSON.stringify(getCfg());
		if(typeof GM_setClipboard == 'function') {
			GM_setClipboard(s);
			wn.alert('Settings copied to clipboard!');
		} else {
			wn.alert(s);
		}
	};
	var imp = function(e) {
		drop(e);
		var s = wn.prompt('Paste settings:');
		if(!s) return;
		init(fixCfg(s));
	};
	var install = function(e) {
		drop(e);
		e.target.parentNode.innerHTML = '<span>Loading...</span><iframe style="width:100%;height:26px;border:0;margin:0;display:none;" src="//w9p.co/userscripts/mpiv/more_host_rules.html" onload="this.style.display=\'\';this.previousElementSibling.style.display=\'none\';"></iframe>';
	};
	var getCfg = function() {
		var cfg = {};
		var delay = parseInt($('delay').value);
		if(!isNaN(delay) && delay >= 0) cfg.delay = delay;
		var scale = parseFloat($('scale').value.replace(',', '.'));
		if(!isNaN(scale)) cfg.scale = Math.max(1, scale);
		cfg.start = $('start-context').selected ? 'context' : ($('start-ctrl').selected ? 'ctrl' : 'auto');
		cfg.zoom = $('zoom-context').selected ? 'context' : ($('zoom-wheel').selected ? 'wheel' : ($('zoom-shift').selected ? 'shift' : 'auto'));
		cfg.center = $('center').checked;
		cfg.imgtab = $('imgtab').checked;
		cfg.close = $('close').selected;
		cfg.preload = $('preload').checked;
		cfg.css = $('css').value.trim();
		cfg.scales = $('scales').value.trim().split(/[,;]*\s+/).map(function(x) { return x.replace(',', '.'); }).filter(function(x) { return !isNaN(parseFloat(x)); });
		cfg.xhr = $('xhr').checked;
		var inps = qsa('input', $('hosts')), lines = [];
		for(var i = 0; i < inps.length; i++) {
			var s = inps[i].value.trim();
			if(s) lines.push(s);
		}
		lines.sort();
		cfg.hosts = lines.join('\n');
		return fixCfg(JSON.stringify(cfg));
	};
	var init = function(cfg) {
		close();
		if(!contains(trusted, hostname)) on(wn, 'message', onMessage);
		addStyle('\
			#mpiv-setup { position:fixed;z-index:2147483647;top:20px;right:20px;padding:20px 30px;background:#eee;width:640px;border:1px solid black; }\
			#mpiv-setup * { color:black;text-align:left;min-height:unset;margin:unset;padding:unset;line-height:15px;font-size:12px;font-family:sans-serif;box-shadow:none; }\
			#mpiv-setup a { color:darkblue!important;text-decoration:underline!important; }\
			#mpiv-setup ul { margin:10px 0 15px 0;padding:0;list-style:none;background:#eee;border:0; }\
			#mpiv-setup input, #mpiv-setup select, #mpiv-css { display:inline;border:1px solid gray;padding:2px;background:white; }\
			#mpiv-css { resize:vertical; height:45px; }\
			#mpiv-scales { width:130px; }\
			#mpiv-setup li { margin:0;padding:7px 0;vertical-align:middle;background:#eee;border:0 }\
			#mpiv-zoom { margin-right: 18px; }\
			#mpiv-delay, #mpiv-scale { width:36px; }\
			#mpiv-cursor, #mpiv-imgtab, #mpiv-xhr, #mpiv-preload { margin-left:18px; }\
			#mpiv-hosts { max-height:150px;overflow-y:auto; padding:2px; margin:4px 0;clear:both; }\
			#mpiv-hosts input, #mpiv-css { width:98%;margin:3px 0; }\
			#mpiv-setup button { width:150px;margin:0 10px;text-align:center; }\
		');
		var div = ce('div');
		div.id = 'mpiv-setup';
		d.body.appendChild(div);
		div.innerHTML = '\
			<div><a href="http://w9p.co/userscripts/mpiv/">Mouseover Popup Image Viewer</a><span style="float:right"><a href="#" id="mpiv-import">Import</a> | <a href="#" id="mpiv-export">Export</a></span></div><ul>\
			<li>Popup: <select><option id="mpiv-start-auto">automatically</option><option id="mpiv-start-context">right click or ctrl</option><option id="mpiv-start-ctrl">ctrl</option></select> <span>after <input id="mpiv-delay" type="text"/> ms</span> <span><input type="checkbox" id="mpiv-preload"/> Start loading immediately</span></li>\
			<li>Only show popup over scaled-down image when natural size is <input id="mpiv-scale" type="text"/> times larger</li>\
			<li><input type="checkbox" id="mpiv-center"/> Always centered <input type="checkbox" id="mpiv-imgtab"/> Run in image tabs <input type="checkbox" id="mpiv-xhr" onclick="return this.checked || confirm(\'Do not disable this unless you spoof the HTTP headers yourself.\')"/> Anti-hotlinking workaround</li>\
			<li>Zoom: <select id="mpiv-zoom"><option id="mpiv-zoom-context">right click or shift</option><option id="mpiv-zoom-wheel">wheel up or shift</option><option id="mpiv-zoom-shift">shift</option><option id="mpiv-zoom-auto">automatically</option></select> Custom scale factors: <input type="text" id="mpiv-scales" placeholder="e.g. 0 0.5 1* 2"/> <span title="values smaller than non-zoomed size are ignored, 0 = fit to window, 0! = same as 0 but also removes smaller values, asterisk after value marks default zoom factor (e.g. 1*)" style="cursor:help">(?)</span></li>\
			<li>If zooming out further is not possible, <select><option>stay in zoom mode</option><option id="mpiv-close">close popup</option></select></li>\
			<li><a href="http://w9p.co/userscripts/mpiv/css.html" target="_blank">Custom CSS:</a><div><textarea id="mpiv-css" spellcheck="false"></textarea></li>\
			<li><a href="http://w9p.co/userscripts/mpiv/host_rules.html" target="_blank">Custom host rules:</a><input type="text" id="mpiv-search" placeholder="Search" style="display:none;float:right;width:70px;padding:1px 2px;font-size:10px;"/><div id="mpiv-hosts"><input type="text" spellcheck="false"></div></li>\
			<li><a href="#" id="mpiv-install">Install rule from repository...</a></li>\
			</ul><div style="text-align:center"><button id="mpiv-ok">OK</button><button id="mpiv-cancel">Cancel</button></div>';
		if(cfg.hosts) {
			var parent = $('hosts');
			var lines = cfg.hosts.split(/[\r\n]+/);
			for(var i = 0, s; i < lines.length && (s = lines[i]); i++) {
				var inp = parent.firstElementChild.cloneNode();
				inp.value = s;
				parent.appendChild(inp);
				check({target:inp});
			}
			if(lines.length > 5 || setup.search) {
				var se = $('search'), sf = function() {
					var inps = qsa('input', $('hosts')), s = se.value.toLowerCase();
					setup.search = s;
					for(var i = 0; i < inps.length; i++) {
						inps[i].style.display = !inps[i].value || contains(inps[i].value.toLowerCase(), s) ? '' : 'none';
					}
				};
				on(se, 'input', sf);
				se.value = setup.search || '';
				if(se.value) sf();
				se.style.display = '';
			}
		}
		on($('start-auto').parentNode, 'change', update);
		on($('cancel'), 'click', close);
		on($('export'), 'click', exp);
		on($('import'), 'click', imp);
		on($('hosts'), 'input', check);
		on($('install'), 'click', install);
		on($('ok'), 'click', function() {
			saveCfg(getCfg());
			hosts = loadHosts();
			close();
		});
		$('delay').value = cfg.delay;
		$('scale').value = cfg.scale;
		$('center').checked = cfg.center;
		$('imgtab').checked = cfg.imgtab;
		$('close').selected = cfg.close;
		$('preload').checked = cfg.preload;
		$('css').value = cfg.css;
		$('scales').value = cfg.scales.join(' ');
		$('xhr').checked = cfg.xhr;
		$('zoom-' + cfg.zoom).selected = true;
		$('start-' + cfg.start).selected = true;
		update();
		var free = viewRect().height - div.offsetHeight - 60;
		$('hosts').style.maxHeight = parseInt($('hosts').offsetHeight + 0.8 * free) + 'px';
		$('css').style.height = parseInt($('css').offsetHeight + 0.2 * free) + 'px';
		div = null;
	};
	init(loadCfg());
}

addStyle('\
	#mpiv-bar { position:fixed;z-index:2147483647;left:0;right:0;top:0;transform:scaleY(0);-webkit-transform:scaleY(0);transform-origin:top;-webkit-transform-origin:top;transition:transform 500ms ease 1000ms;-webkit-transition:-webkit-transform 500ms ease 1000ms;text-align:center;font-family:sans-serif;font-size:15px;font-weight:bold;background:rgba(0, 0, 0, 0.6);color:white;padding:4px 10px; }\
	#mpiv-bar.mpiv-show { transform:scaleY(1);-webkit-transform:scaleY(1); }\
	#mpiv-popup.mpiv-show { display:inline; }\
	#mpiv-popup { display:none;border:1px solid gray;box-sizing:content-box;background-color:white;position:fixed;z-index:2147483647;margin:0;max-width:none;max-height:none;will-change:display,width,height,left,top;cursor:none; }\
	.mpiv-loading:not(.mpiv-preloading) * { cursor:wait!important; }\
	.mpiv-edge #mpiv-popup { cursor:default; }\
	.mpiv-error * { cursor:not-allowed!important; }\
	.mpiv-ready *, .mpiv-large * { cursor:zoom-in!important; cursor:-webkit-zoom-in!important; }\
	.mpiv-shift * { cursor:default!important; }');
on(d, 'mouseover', onMouseOver);
if(contains(hostname, 'google')) {
	var node = d.getElementById('main');
	if(node) on(node, 'mouseover', onMouseOver);
} else if(contains(trusted, hostname)) {
	on(wn, 'message', onMessage);
	on(d, 'click', function(e) {
		var t = e.target;
		if(e.which != 1 || !/BLOCKQUOTE|CODE|PRE/.test(tag(t) + tag(t.parentNode)) || !/^\s*\{\s*".+:.+\}\s*$/.test(t.textContent)) return;
		wn.postMessage('mpiv-rule ' + t.textContent, '*');
		e.preventDefault();
	});
}