Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
  $(function() {
    var server;
   
    // get a translated string
    function i18n(key) {
      if (!(key in i18nData) || !(uiLang in i18nData[key])) {
        return key;
      }
      return i18nData[key][uiLang];
    }

    // request the fastcci db over HTTPS (no streaming)
    function requestXHR(params, callback) {
      $.get(location.protocol + server.val(), params)
        .done(function(data) {
          var res = data.split('\n');
          for (var i = 0; i < res.length; ++i) {
            callback(res[i]);
          }
        });
    }

    // request the fastcci db using a JS callback (no streaming, no CORS)
    function requestJS(params, callback) {
      window.fastcciCallback = function(res) {
        for (var i = 0; i < res.length; ++i) {
          callback(res[i]);
        }
      };
      $.getScript(location.protocol + server.val() + '?t=js&' + $.param(params));
    }

    // request the fastcci db over a WebSocket (streaming with progressive status updates)
    function requestSocket(params, callback) {
      var ws = new WebSocket('wss:' + server.val() + '?' + $.param(params));
      //ws.onmessage = function(event) { setTimeout(function() {callback(event.data);}, 0); };
      ws.onmessage = function (event) {
        callback(event.data);
      };
      ws.onerror = function(event) {
        // We should fall back to JS if the WS connection throws an error
        // However current Chrome versions throw a non-fatal error (reserved bits)
        // I'll need to fix this first before I can reenable the fallback :-/
        //mw.notify('Still connecting...');
        //request = requestJS;
        //request(params, callback);
      };
    }

    // determine request method (requestSocket > requestXHR > requestJS)

    // websockets are currently broken on the server side
    request = ('WebSocket' in window) ? requestSocket : (('withCredentials' in new XMLHttpRequest()) ? requestXHR : requestJS);

    // request wrapper that prepares the gallery
    function fetchGallery(params) {
      // append to result gallery
      function echoResults(txt) {
        $('#fastcci_output').append($('<div>').text(txt));
      }

      request(params, echoResults);
    }

	if (wgPageName == "User:Dschwen/FastCCI")
	{
		var button = $('<button>Go</button>');
		server = $('<input id="server_name" value="fastcci2.wmflabs.org" width=30/>')
		$('#fastcci_ui').append(server);
		$('#fastcci_ui').append(button);
	
		button.click(function(e){
			$('#fastcci_output').text('Sending request');
		    thisPageId = 1316; // Category:Paris
		    fetchGallery({ c1: thisPageId, d1: 15, s: 200, a: 'fqv' });
		});
	}
  });

$(function() {
	var icon_down = 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Thumbs_down_font_awesome.svg/60px-Thumbs_down_font_awesome.svg.png';
	var icon_up   = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Thumbs_up_font_awesome.svg/60px-Thumbs_up_font_awesome.svg.png';
	
	if ('MutationObserver' in window) {
		// load the viewer
		function launchViewer() {
			var file = $('.mw-mmv-description-page-button').attr('href'),
				prefix = "https://commons.wikimedia.org/wiki/File:";
				
			if (file.substr(0, prefix.length) === prefix) {
				document.location = 'https://tools.wmflabs.org/panoviewer/#' + file.substr(prefix.length);
			}
		}
		
		// analyze mutation list with this callback
		function analyzeMutations(ml) {
			ml.forEach(function(mi) {
				// look for multimedia viewer
				if (mi.addedNodes[0] && mi.addedNodes[0].classList[0] === 'mw-mmv-wrapper')
				{
					console.log('MMV Wrapper added', mi);
					var pi = $('.mw-mmv-pre-image');
					if (pi.length > 0 && !pi.data('panoviever_initialized')) {
						var btn = $('<img>')
							.attr('src', "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Spherical_pano_icon_white.svg/24px-Spherical_pano_icon_white.svg.png")
							.attr('srcset', "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Spherical_pano_icon_white.svg/48px-Spherical_pano_icon_white.svg.png 2x")
							.css({
								position: 'fixed',
								marginRight: '14px',
								bottom: '179px', right: '5px',
								opacity: 0.8, cursor: 'pointer'
							})
							.on('click', launchViewer)
						pi.append(btn);
						pi.data('panoviever_initialized', true);
					}
				}
			});
		}
		
		// set up mutation observer
		m = new MutationObserver(analyzeMutations);
		// observe only changes in the childen list of the document body
		m.observe(document.body, { childList: true });
	}
});