// ==UserScript==
// @name        xch
// @description Extension for 38chan
// @version     0.3.2
// @namespace   dnsev
// @grant       GM_xmlhttpRequest
// @grant       GM_info
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_listValues
// @grant       GM_deleteValue
// @include     *://boards.38chan.net/*
// @run-at      document-start
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAArElEQVRYw2NgGAWjYKQDRgYGhv9EqGEgQh1Z+pkGOgRYYIy3bKgSwr/gTBSX/y7Db+C3dAjNr4xd/uNdVPnBEwIwH8NCAkbDxNF9ztqFw4fppDlgUIQASirFFRJ4fE4ol/wf9CGA1SfoIYEnf+NN7UMiDWCNKzw+R1ePNSQIlQODKgTw+hxeDqCVhEi5gaK6ZEjUBSg+hoUArjrhG4kOGPAQGG0PjIJRMApGAQAW/TQofPgeiQAAAABJRU5ErkJggg==
// @updateURL   https://raw.github.com/dnsev/xch/master/builds/main/xch.meta.js
// @downloadURL https://raw.github.com/dnsev/xch/master/builds/main/xch.user.js
// ==/UserScript==


////////////////////////////////////////////////////////////////////////////////
//{ jquery.js
////////////////////////////////////////////////////////////////////////////////
/*! jQuery v2.0.3 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
*/
(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)
};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ct={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1></$2>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1></$2>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(xt[0].contentWindow||xt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=Mt(e,t),xt.detach()),Nt[e]=n),n}function Mt(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,t){x.cssHooks[t]={get:function(e,n,r){return n?0===e.offsetWidth&&bt.test(x.css(e,"display"))?x.swap(e,Et,function(){return Pt(e,t,r)}):Pt(e,t,r):undefined},set:function(e,n,r){var i=r&&qt(e);return Ot(e,n,r?Ft(e,t,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,t){return t?x.swap(e,{display:"inline-block"},vt,[e,"marginRight"]):undefined}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,t){x.cssHooks[t]={get:function(e,n){return n?(n=vt(e,t),Ct.test(n)?x(e).position()[t]+"px":n):undefined}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+jt[r]+t]=o[r]||o[r-2]||o[0];return i}},wt.test(e)||(x.cssHooks[e+t].set=Ot)});var Wt=/%20/g,$t=/\[\]$/,Bt=/\r?\n/g,It=/^(?:submit|button|image|reset|file)$/i,zt=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&zt.test(this.nodeName)&&!It.test(e)&&(this.checked||!ot.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(Bt,"\r\n")}}):{name:t.name,value:n.replace(Bt,"\r\n")}}).get()}}),x.param=function(e,t){var n,r=[],i=function(e,t){t=x.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(t===undefined&&(t=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){i(this.name,this.value)});else for(n in e)_t(n,e[n],t,i);return r.join("&").replace(Wt,"+")};function _t(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||$t.test(e)?r(e,i):_t(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)_t(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)
},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var Xt,Ut,Yt=x.now(),Vt=/\?/,Gt=/#.*$/,Jt=/([?&])_=[^&]*/,Qt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Kt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Zt=/^(?:GET|HEAD)$/,en=/^\/\//,tn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,nn=x.fn.load,rn={},on={},sn="*/".concat("*");try{Ut=i.href}catch(an){Ut=o.createElement("a"),Ut.href="",Ut=Ut.href}Xt=tn.exec(Ut.toLowerCase())||[];function un(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(w)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function ln(e,t,n,r){var i={},o=e===on;function s(a){var u;return i[a]=!0,x.each(e[a]||[],function(e,a){var l=a(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):undefined:(t.dataTypes.unshift(l),s(l),!1)}),u}return s(t.dataTypes[0])||!i["*"]&&s("*")}function cn(e,t){var n,r,i=x.ajaxSettings.flatOptions||{};for(n in t)t[n]!==undefined&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,t,n){if("string"!=typeof e&&nn)return nn.apply(this,arguments);var r,i,o,s=this,a=e.indexOf(" ");return a>=0&&(r=e.slice(a),e=e.slice(0,a)),x.isFunction(t)?(n=t,t=undefined):t&&"object"==typeof t&&(i="POST"),s.length>0&&x.ajax({url:e,type:i,dataType:"html",data:t}).done(function(e){o=arguments,s.html(r?x("<div>").append(x.parseHTML(e)).find(r):e)}).complete(n&&function(e,t){s.each(n,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ut,type:"GET",isLocal:Kt.test(Xt[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":sn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?cn(cn(e,x.ajaxSettings),t):cn(x.ajaxSettings,e)},ajaxPrefilter:un(rn),ajaxTransport:un(on),ajax:function(e,t){"object"==typeof e&&(t=e,e=undefined),t=t||{};var n,r,i,o,s,a,u,l,c=x.ajaxSetup({},t),p=c.context||c,f=c.context&&(p.nodeType||p.jquery)?x(p):x.event,h=x.Deferred(),d=x.Callbacks("once memory"),g=c.statusCode||{},m={},y={},v=0,b="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===v){if(!o){o={};while(t=Qt.exec(i))o[t[1].toLowerCase()]=t[2]}t=o[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===v?i:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return v||(e=y[n]=y[n]||e,m[e]=t),this},overrideMimeType:function(e){return v||(c.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>v)for(t in e)g[t]=[g[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||b;return n&&n.abort(t),k(0,t),this}};if(h.promise(T).complete=d.add,T.success=T.done,T.error=T.fail,c.url=((e||c.url||Ut)+"").replace(Gt,"").replace(en,Xt[1]+"//"),c.type=t.method||t.type||c.method||c.type,c.dataTypes=x.trim(c.dataType||"*").toLowerCase().match(w)||[""],null==c.crossDomain&&(a=tn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===Xt[1]&&a[2]===Xt[2]&&(a[3]||("http:"===a[1]?"80":"443"))===(Xt[3]||("http:"===Xt[1]?"80":"443")))),c.data&&c.processData&&"string"!=typeof c.data&&(c.data=x.param(c.data,c.traditional)),ln(rn,c,t,T),2===v)return T;u=c.global,u&&0===x.active++&&x.event.trigger("ajaxStart"),c.type=c.type.toUpperCase(),c.hasContent=!Zt.test(c.type),r=c.url,c.hasContent||(c.data&&(r=c.url+=(Vt.test(r)?"&":"?")+c.data,delete c.data),c.cache===!1&&(c.url=Jt.test(r)?r.replace(Jt,"$1_="+Yt++):r+(Vt.test(r)?"&":"?")+"_="+Yt++)),c.ifModified&&(x.lastModified[r]&&T.setRequestHeader("If-Modified-Since",x.lastModified[r]),x.etag[r]&&T.setRequestHeader("If-None-Match",x.etag[r])),(c.data&&c.hasContent&&c.contentType!==!1||t.contentType)&&T.setRequestHeader("Content-Type",c.contentType),T.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+("*"!==c.dataTypes[0]?", "+sn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)T.setRequestHeader(l,c.headers[l]);if(c.beforeSend&&(c.beforeSend.call(p,T,c)===!1||2===v))return T.abort();b="abort";for(l in{success:1,error:1,complete:1})T[l](c[l]);if(n=ln(on,c,t,T)){T.readyState=1,u&&f.trigger("ajaxSend",[T,c]),c.async&&c.timeout>0&&(s=setTimeout(function(){T.abort("timeout")},c.timeout));try{v=1,n.send(m,k)}catch(C){if(!(2>v))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,t,o,a){var l,m,y,b,w,C=t;2!==v&&(v=2,s&&clearTimeout(s),n=undefined,i=a||"",T.readyState=e>0?4:0,l=e>=200&&300>e||304===e,o&&(b=pn(c,T,o)),b=fn(c,b,T,l),l?(c.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(x.lastModified[r]=w),w=T.getResponseHeader("etag"),w&&(x.etag[r]=w)),204===e||"HEAD"===c.type?C="nocontent":304===e?C="notmodified":(C=b.state,m=b.data,y=b.error,l=!y)):(y=C,(e||!C)&&(C="error",0>e&&(e=0))),T.status=e,T.statusText=(t||C)+"",l?h.resolveWith(p,[m,C,T]):h.rejectWith(p,[T,C,y]),T.statusCode(g),g=undefined,u&&f.trigger(l?"ajaxSuccess":"ajaxError",[T,c,l?m:y]),d.fireWith(p,[T,C]),u&&(f.trigger("ajaxComplete",[T,c]),--x.active||x.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,undefined,t,"script")}}),x.each(["get","post"],function(e,t){x[t]=function(e,n,r,i){return x.isFunction(n)&&(i=i||r,r=n,n=undefined),x.ajax({url:e,type:t,dataType:i,data:n,success:r})}});function pn(e,t,n){var r,i,o,s,a=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),r===undefined&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}s||(s=i)}o=o||s}return o?(o!==u[0]&&u.unshift(o),n[o]):undefined}function fn(e,t,n,r){var i,o,s,a,u,l={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)l[s.toLowerCase()]=e.converters[s];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(s=l[u+" "+o]||l["* "+o],!s)for(i in l)if(a=i.split(" "),a[1]===o&&(s=l[u+" "+a[0]]||l["* "+a[0]])){s===!0?s=l[i]:l[i]!==!0&&(o=a[0],c.unshift(a[1]));break}if(s!==!0)if(s&&e["throws"])t=s(t);else try{t=s(t)}catch(p){return{state:"parsererror",error:s?p:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===undefined&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),x.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(r,i){t=x("<script>").prop({async:!0,charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&i("error"===e.type?404:200,e.type)}),o.head.appendChild(t[0])},abort:function(){n&&n()}}}});var hn=[],dn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=hn.pop()||x.expando+"_"+Yt++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,s,a=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&!(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");return a||"jsonp"===t.dataTypes[0]?(i=t.jsonpCallback=x.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,a?t[a]=t[a].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(Vt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return s||x.error(i+" was not called"),s[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){s=arguments},r.always(function(){e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,hn.push(i)),s&&x.isFunction(o)&&o(s[0]),s=o=undefined}),"script"):undefined}),x.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(e){}};var gn=x.ajaxSettings.xhr(),mn={0:200,1223:204},yn=0,vn={};e.ActiveXObject&&x(e).on("unload",function(){for(var e in vn)vn[e]();vn=undefined}),x.support.cors=!!gn&&"withCredentials"in gn,x.support.ajax=gn=!!gn,x.ajaxTransport(function(e){var t;return x.support.cors||gn&&!e.crossDomain?{send:function(n,r){var i,o,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(i in e.xhrFields)s[i]=e.xhrFields[i];e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(i in n)s.setRequestHeader(i,n[i]);t=function(e){return function(){t&&(delete vn[o],t=s.onload=s.onerror=null,"abort"===e?s.abort():"error"===e?r(s.status||404,s.statusText):r(mn[s.status]||s.status,s.statusText,"string"==typeof s.responseText?{text:s.responseText}:undefined,s.getAllResponseHeaders()))}},s.onload=t(),s.onerror=t("error"),t=vn[o=yn++]=t("abort"),s.send(e.hasContent&&e.data||null)},abort:function(){t&&t()}}:undefined});var xn,bn,wn=/^(?:toggle|show|hide)$/,Tn=RegExp("^(?:([+-])=|)("+b+")([a-z%]*)$","i"),Cn=/queueHooks$/,kn=[An],Nn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Tn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),s=(x.cssNumber[e]||"px"!==o&&+r)&&Tn.exec(x.css(n.elem,e)),a=1,u=20;if(s&&s[3]!==o){o=o||s[3],i=i||[],s=+r||1;do a=a||".5",s/=a,x.style(n.elem,e,s+o);while(a!==(a=n.cur()/r)&&1!==a&&--u)}return i&&(s=n.start=+s||+r||0,n.unit=o,n.end=i[1]?s+(i[1]+1)*i[2]:+i[2]),n}]};function En(){return setTimeout(function(){xn=undefined}),xn=x.now()}function Sn(e,t,n){var r,i=(Nn[t]||[]).concat(Nn["*"]),o=0,s=i.length;for(;s>o;o++)if(r=i[o].call(n,t,e))return r}function jn(e,t,n){var r,i,o=0,s=kn.length,a=x.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=xn||En(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,s=0,u=l.tweens.length;for(;u>s;s++)l.tweens[s].run(o);return a.notifyWith(e,[l,o,n]),1>o&&u?n:(a.resolveWith(e,[l]),!1)},l=a.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:xn||En(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?a.resolveWith(e,[l,t]):a.rejectWith(e,[l,t]),this}}),c=l.props;for(Dn(c,l.opts.specialEasing);s>o;o++)if(r=kn[o].call(l,e,c,l.opts))return r;return x.map(c,Sn,l),x.isFunction(l.opts.start)&&l.opts.start.call(e,l),x.fx.timer(x.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function Dn(e,t){var n,r,i,o,s;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),s=x.cssHooks[r],s&&"expand"in s){o=s.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(jn,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Nn[n]=Nn[n]||[],Nn[n].unshift(t)},prefilter:function(e,t){t?kn.unshift(e):kn.push(e)}});function An(e,t,n){var r,i,o,s,a,u,l=this,c={},p=e.style,f=e.nodeType&&Lt(e),h=q.get(e,"fxshow");n.queue||(a=x._queueHooks(e,"fx"),null==a.unqueued&&(a.unqueued=0,u=a.empty.fire,a.empty.fire=function(){a.unqueued||u()}),a.unqueued++,l.always(function(){l.always(function(){a.unqueued--,x.queue(e,"fx").length||a.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(p.display="inline-block")),n.overflow&&(p.overflow="hidden",l.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],wn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show")){if("show"!==i||!h||h[r]===undefined)continue;f=!0}c[r]=h&&h[r]||x.style(e,r)}if(!x.isEmptyObject(c)){h?"hidden"in h&&(f=h.hidden):h=q.access(e,"fxshow",{}),o&&(h.hidden=!f),f?x(e).show():l.done(function(){x(e).hide()}),l.done(function(){var t;q.remove(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)s=Sn(f?h[r]:0,r,l),r in h||(h[r]=s.start,f&&(s.end=s.start,s.start="width"===r||"height"===r?1:0))}}function Ln(e,t,n,r,i){return new Ln.prototype.init(e,t,n,r,i)}x.Tween=Ln,Ln.prototype={constructor:Ln,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=Ln.propHooks[this.prop];return e&&e.get?e.get(this):Ln.propHooks._default.get(this)},run:function(e){var t,n=Ln.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ln.propHooks._default.set(this),this}},Ln.prototype.init.prototype=Ln.prototype,Ln.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Ln.propHooks.scrollTop=Ln.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(qn(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Lt).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),s=function(){var t=jn(this,x.extend({},e),o);(i||q.get(this,"finish"))&&t.stop(!0)};return s.finish=s,i||o.queue===!1?this.each(s):this.queue(o.queue,s)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=undefined),t&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=x.timers,s=q.get(this);if(i)s[i]&&s[i].stop&&r(s[i]);else for(i in s)s[i]&&s[i].stop&&Cn.test(i)&&r(s[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));(t||!n)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=q.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,s=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;s>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function qn(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=jt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:qn("show"),slideUp:qn("hide"),slideToggle:qn("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=Ln.prototype.init,x.fx.tick=function(){var e,t=x.timers,n=0;for(xn=x.now();t.length>n;n++)e=t[n],e()||t[n]!==e||t.splice(n--,1);t.length||x.fx.stop(),xn=undefined},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){bn||(bn=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(bn),bn=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===undefined?this:this.each(function(t){x.offset.setOffset(this,e,t)});var t,n,i=this[0],o={top:0,left:0},s=i&&i.ownerDocument;if(s)return t=s.documentElement,x.contains(t,i)?(typeof i.getBoundingClientRect!==r&&(o=i.getBoundingClientRect()),n=Hn(s),{top:o.top+n.pageYOffset-t.clientTop,left:o.left+n.pageXOffset-t.clientLeft}):o},x.offset={setOffset:function(e,t,n){var r,i,o,s,a,u,l,c=x.css(e,"position"),p=x(e),f={};"static"===c&&(e.style.position="relative"),a=p.offset(),o=x.css(e,"top"),u=x.css(e,"left"),l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1,l?(r=p.position(),s=r.top,i=r.left):(s=parseFloat(o)||0,i=parseFloat(u)||0),x.isFunction(t)&&(t=t.call(e,n,a)),null!=t.top&&(f.top=t.top-a.top+s),null!=t.left&&(f.left=t.left-a.left+i),"using"in t?t.using.call(e,f):p.css(f)}},x.fn.extend({position:function(){if(this[0]){var e,t,n=this[0],r={top:0,left:0};return"fixed"===x.css(n,"position")?t=n.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(r=e.offset()),r.top+=x.css(e[0],"borderTopWidth",!0),r.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-r.top-x.css(n,"marginTop",!0),left:t.left-r.left-x.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,n){var r="pageYOffset"===n;x.fn[t]=function(i){return x.access(this,function(t,i,o){var s=Hn(t);return o===undefined?s?s[n]:t[i]:(s?s.scrollTo(r?e.pageXOffset:o,r?o:e.pageYOffset):t[i]=o,undefined)},t,i,arguments.length,null)}});function Hn(e){return x.isWindow(e)?e:9===e.nodeType&&e.defaultView}x.each({Height:"height",Width:"width"},function(e,t){x.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){x.fn[r]=function(r,i){var o=arguments.length&&(n||"boolean"!=typeof r),s=n||(r===!0||i===!0?"margin":"border");return x.access(this,function(t,n,r){var i;return x.isWindow(t)?t.document.documentElement["client"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):r===undefined?x.css(t,n,s):x.style(t,n,r,s)},t,o?r:undefined,o,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}),"object"==typeof e&&"object"==typeof e.document&&(e.jQuery=e.$=x)})(window);
////////////////////////////////////////////////////////////////////////////////
//} /jquery.js
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
//{ Userscript
////////////////////////////////////////////////////////////////////////////////



// Scope
var xch = (function () {
	"use strict";

	/*!debug!*/var assert = function (condition, message) { if (!condition) { var stack = ""; try { undefined.undefined = undefined; } catch (e) { stack = e.stack.toString(); } alert("Assertion failed; " + (message || "") + "\n\n" + stack); } }

	// Semi-globals
	var style = null;
	var communication = null;
	var location = null;
	var info = null;
	var content = null;
	var api = null;
	var settings = null;

	// Vars
	var xch = {

		// Script data
		script: {

			prefix: "xch",
			name: "xch",
			version: "inline", /*!auto-version:"inline"!*/
			homepage: "http://dnsev.github.com/xch/",
			site: "38chan",
			update_homepage: "http://dnsev.github.com/xch/#!install/update",
			update_url: "",
			browser: {
				firefox: false,
				chrome: false
			},

			execute_asap: (function () {

				var execute_asap = function (callback, check) {
					if (check.call(this)) {
						callback.call(this);
					}
					else {
						var timeout = setTimeout(function () {
							execute_asap.call(this, callback, check);
						}, 10);
					}
				};

				return execute_asap;

			})(),

			initialize: function () {
				// Get the name and version
				try {
					// Greasemonkey
					xch.script.name = GM_info.script.name.toString();
					xch.script.version = GM_info.script.version.toString();
				}
				catch (e) {
					try {
						// Tampermonkey
						xch.script.name = GM_getMetadata("name").toString();
						xch.script.version = GM_getMetadata("version").toString();
					}
					catch (e) {
						// Not applicable
					}
				}

				// Update URL
				this.update_url = this.update_homepage;
				try {
					// Greasemonkey
					var m = /\/\/\s*@downloadURL\s+(\S+)/.exec(GM_info.scriptMetaStr);
					if (m) {
						xch.script.update_url = m[1];
					}
				}
				catch (e) {
					try {
						// Tampermonkey
						xch.script.update_url = GM_getMetadata("downloadURL").toString().trim();
					}
					catch (e) {
					}
				}

				// Local storage functions
				var okay = false;
				try {
					okay = !(!GM_getValue || (GM_getValue.toString && GM_getValue.toString().indexOf("not supported") >= 0));
				}
				catch (e) {
				}
				if (okay) {
					xch.get_value_raw = function (key, def) {
						var val = GM_getValue(key, undefined);
						return (val === undefined ? def : val);
					};
					xch.set_value_raw = function (key, value) {
						return GM_setValue(key, value);
					};
					xch.get_value = function (key, def) {
						var val = GM_getValue(key);
						if (val === undefined) return def;
						try {
							return JSON.parse(val);
						}
						catch (e) {
							return def;
						}
					};
					xch.set_value = function (key, value) {
						return GM_setValue(key, JSON.stringify(value));
					};
					xch.delete_value = function (key) {
						return GM_deleteValue(key);
					};
					xch.list_value_keys = function () {
						return GM_listValues();
					};
				}
				else {
					xch.get_value_raw = function (key, def) {
						var val = window.localStorage.getItem(key, null);
						return (val === null ? def : val);
					};
					xch.set_value_raw = function (key, value) {
						return window.localStorage.setItem(key, value);
					};
					xch.get_value = function (key, def) {
						var val = window.localStorage.getItem(key);
						if (val === null) return def;
						try {
							return JSON.parse(val);
						}
						catch (e) {
							return def;
						}
					};
					xch.set_value = function (key, value) {
						return window.localStorage.setItem(key, JSON.stringify(value));
					};
					xch.delete_value = function (key) {
						return window.localStorage.removeItem(key);
					};
					xch.list_value_keys = function () {
						return Object.keys(window.localStorage);
					};
				}

				// Site storage
				var site_prefix = xch.script.prefix + "_" + xch.script.site + "_";
				xch.site_get_value_raw = function (key, def) {
					return xch.get_value_raw(site_prefix + key, def);
				};
				xch.site_set_value_raw = function (key, value) {
					return xch.set_value_raw(site_prefix + key, value);
				};
				xch.site_get_value = function (key, def) {
					return xch.get_value(site_prefix + key, def);
				};
				xch.site_set_value = function (key, value) {
					return xch.set_value(site_prefix + key, value);
				};
				xch.site_delete_value = function (key) {
					return xch.delete_value(site_prefix + key);
				};
				xch.site_list_value_keys = function () {
					var k = xch.list_value_keys(), keys = [];
					for (var i = 0; i < k.length; ++i) {
						if (k[i].indexOf(site_prefix) == 0) keys.push(k[i]);
					}
					return keys;
				};

				// Browser
				var ua = navigator.userAgent.toString();
				if (ua.indexOf(" Firefox/") >= 0) {
					xch.script.browser.firefox = true;
				}
				else if (ua.indexOf(" Chrome/") >= 0) {
					xch.script.browser.chrome = true;
				}
			},
			register: function (html, addon_name, addon_version) {
				// Regsiter the addon in the HTML
				if (html.attr("addons")) {
					html.attr("addons", html.attr("addons") + "," + addon_name);
				}
				else {
					html.attr("addons", addon_name);
				}
			},
			block_scripts: function () {
				// Blocks website scripts
				var removal_count = 0;
				var removal_total = 10;

				var bse_callback;
				window.addEventListener("beforescriptexecute", (bse_callback = (function (event) {
					var ret = true;

					if (event.target.src) {
						// Remove main.js
						if (
							/main\.js$/.test(event.target.src) ||
							/inline-expanding\.js$/.test(event.target.src) ||
							/auto-reload\.js$/.test(event.target.src) ||
							/post-hover\.js$/.test(event.target.src) ||
							/show-backlinks\.js$/.test(event.target.src) ||
							/show-op\.js$/.test(event.target.src) ||
							/jquery\.min\.js$/.test(event.target.src)
						) {
							ret = false;
						}
					}
					else {
						// Remove rememberStuff();
						if (event.target.innerHTML.length < 100) {
							if (
								/^\s*rememberStuff\(\);\s*$/.test(event.target.innerHTML)
							) {
								ret = false;
							}
							else if (
								/^\s*var configRoot=/.test(event.target.innerHTML)
							) {
								ret = false;
							}
							else if (
								/^\s*ready\(\);\s*$/.test(event.target.innerHTML)
							) {
								ret = false;
							}
						}
					}

					if (!ret) {
						// Stop
						event.preventDefault();
						event.stopPropagation();
						// Remove
						if (event.target.parentNode) {
							event.target.parentNode.removeChild(event.target);
						}
						// Remove when done
						if (++removal_count >= removal_total) {
							window.removeEventListener("beforescriptexecute", bse_callback, true);
						}
					}
					return ret;
				})), true);
			},
			startup: function () {
				// Initialize script
				xch.script.initialize();

				// Meta
				var meta = new xch.Meta();
				if (meta.page_okay) {
					// Block
					xch.script.block_scripts();

					// Location
					location = new xch.Location();

					// API
					api = new xch.API();

					// Communication
					communication = new xch.Communication();

					// Validation setting
					var validated = undefined;

					// Styling
					xch.script.execute_asap(
						function () {
							if (validated === undefined && style == null) {
								var head = document.querySelector("head");
								if (head != null) {
									style = new xch.Style($(head));
								}
							}
						},
						function () {
							return (document.readyState == "complete" || document.readyState == "interactive") && document.querySelector("head") != null;
						}
					);

					// HTML ready
					$(document).ready(function() {
						// Get html
						var html = $("html");

						// Validate
						if ((validated = meta.validate(html))) {
							// Ready
							communication.ready(xch.script.site);

							// Register addon
							xch.script.register(html, xch.script.name, xch.script.version);

							// Settings
							settings = new xch.Settings();

							// Info
							info = new xch.Information(meta, html);

							// Styling
							if (style == null) {
								style = new xch.Style(html.find("head"));
							}
							style.stylize(html);

							// Context
							var context = new xch.Content.Context({
								info: info,
								html: html,
								post_queue: null,
								html_target: null,
								stylize: true,
								is_new: true
							});

							// Content
							content = new xch.Content(context);
							content.parse(context);

							// Update
							var updater = new xch.ScriptUpdater();
							updater.execute();
						}
						else {
							// Remove style
							if (style != null) {
								style.remove();
								style = null;
							}

							// Remove other objects
							location.remove();
							location = null;

							api.remove();
							api = null;

							communication.remove();
							communication = null;
						}
					});

				}
			}

		},

		mime_types: {
			"image/jpeg": {
				name: "JPEG Image",
				extensions: [".jpg",".jpeg"]
			},
			"image/gif": {
				name: "Animated GIF",
				extensions: [".gif"]
			},
			"image/png": {
				name: "PNG Image",
				extensions: [".png"]
			},
			"image/bmp": {
				name: "BMP Image",
				extensions: [".bmp"]
			},
			"video/webm": {
				name: "Webm Video",
				extensions: [".webm"]
			}
		},
		video_mime_types: [ "video/webm" ],


		// Helping functions
		get_value_raw: null,
		set_value_raw: null,
		get_value: null,
		set_value: null,
		delete_value: null,
		list_value_keys: null,

		site_get_value_raw: null,
		site_set_value_raw: null,
		site_get_value: null,
		site_set_value: null,
		site_delete_value: null,
		site_list_value_keys: null,

		ajax: function (data) {
			var gm = (data && data.gm);

			// XHR or GM
			var xhr;
			if (gm && !(xch.script.inline || xch.script.browser.chrome)) {
				// Create
				xhr = {
					method: (data.method || "GET"),
					url: data.url,
					upload: {}
				};

				// Data
				if (data.post_data) {
					arg.data = data.post_data;
				}

				// Return type
				if (data.return_type == "arraybuffer") {
					arg.overrideMimeType = "text/plain; charset=x-user-defined";
				}
				else if (data.return_type == "html") {
					arg.responseType = "document";
				}
				else if (data.return_type == "json") {
					arg.responseType = "json";
				}
				else {
					arg.responseType = "text";
				}
			}
			else {
				// Create
				xhr = new XMLHttpRequest();

				// Open
				xhr.open(data.method || "GET", data.url, true);
				if (data.cred) xhr.withCredentials = true;

				// Return type
				if (data.return_type == "arraybuffer") {
					xhr.overrideMimeType("text/plain; charset=x-user-defined");
					xhr.responseType = "arraybuffer";
				}
				else if (data.return_type == "html") {
					xhr.responseType = "document";
				}
				else if (data.return_type == "json") {
					xhr.responseType = "json";
				}
				else {
					xhr.responseType = "text";
				}
			}


			// Events
			var fcn = typeof(function(){});
			var on = (data ? data.on : null) || {};
			var on_complete = (typeof(on.complete) == fcn ? on.complete : null);
			var on_load = (typeof(on.load) == fcn ? on.load : null);
			var on_error = (typeof(on.error) == fcn ? on.error : null);
			var on_abort = (typeof(on.abort) == fcn ? on.abort : null);
			var on_progress = (typeof(on.progress) == fcn ? on.progress : null);
			var on_upload_load = (on.upload && typeof(on.upload.load) == fcn ? on.upload.load : null);
			var on_upload_abort = (on.upload && typeof(on.upload.abort) == fcn ? on.upload.abort : null);
			var on_upload_error = (on.upload && typeof(on.upload.error) == fcn ? on.upload.error : null);
			var on_upload_progress = (on.upload && typeof(on.upload.progress) == fcn ? on.upload.progress : null);

			// Load
			if (on_load || on_complete) {
				xhr.onload = function (event) {
					if (on_load) {
						var o = (gm ? event : this);
						var response = (gm ? o.responseText : o.response);

						if (o.status == 200) {
							// Convert to array buffer
							if (data.return_type == "arraybuffer") {
								response = new Uint8Array(response);
							}

							// Good callback
							on_load.call(data, {
								okay: true,
								data: data,
								status: o.status,
								response: response,
								status_text: o.statusText
							});
						}
						else {
							// Bad callback
							on_load.call(data, {
								okay: false,
								data: data,
								status: o.status,
								response: response,
								status_text: o.statusText
							});
						}
					}

					// Completed
					if (on_complete) {
						on_complete.call(data, {
							data: data
						});
					}
				};
			}

			// Progress
			if (on_progress) {
				xhr.onprogress = function (event) {
					on_progress.call(data, {
						event: event,
						data: data
					});
				};
			}

			// Error
			if (on_error || on_complete) {
				xhr.onerror = function (event) {
					if (on_error) {
						on_error.call(data, {
							event: event,
							data: data
						});
					}

					// Completed
					if (on_complete) {
						on_complete.call(data, {data:data});
					}
				};
			}

			// Abort
			if (on_abort || on_complete) {
				xhr.onabort = function (event) {
					if (on_abort) {
						on_abort.call(data, {
							event: event,
							data: data
						});
					}

					// Completed
					if (on_complete) {
						on_complete.call(data, {data:data});
					}
				};
			}

			// Upload progress
			if (on_upload_progress) {
				xhr.upload.onprogress = function (event) {
					on_upload_progress.call(data, {
						event: event,
						data: data
					});
				};
			}

			// Upload error
			if (on_upload_error) {
				xhr.upload.onerror = function (event) {
					on_upload_error.call(data, {
						event: event,
						data: data
					});
				};
			}

			// Upload abort
			if (on_upload_abort) {
				xhr.upload.onabort = function (event) {
					on_upload_abort.call(data, {
						event: event,
						data: data
					});
				};
			}

			// Upload done
			if (on_upload_load) {
				xhr.upload.onload = function (event) {
					on_upload_load.call(data, {
						event: event,
						data: data
					});
				};
			}


			// Call
			if (gm) {
				// Send
				var g = GM_xmlhttpRequest(arg);

				// Return
				return g;
			}
			else {
				// Send
				if (data.post_data) {
					//xhr.setRequestHeader("Content-Type", "multipart/form-data");
					xhr.send(data.post_data);
				}
				else {
					xhr.send();
				}

				// Return
				return xhr;
			}
		},

		date: (function () {

			var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
			var months_short = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
			var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
			var days_short = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
			var ordinal = ["st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "st"];

			var format_value = function (date, format) {
				var s = "";
				if (format == 'd') { // Day of the month, 2 digits with leading zeros
					s += date.getDate();
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 'j') { // Day of the month without leading zeros
					s += date.getDate();
				}
				else if (format == 'l') { // A full textual representation of the day of the week
					s += days[date.getDay()];
				}
				else if (format == 'D') { // A textual representation of a day, three letters
					s += days_short[date.getDay()];
				}
				else if (format == 'S') { // English ordinal suffix for the day of the month, 2 characters
					s +=ordinal[date.getDate() - 1];
				}
				else if (format == 'w') { // Numeric representation of the day of the week
					s += date.getDay();
				}
				else if (format == 'F') { // A full textual representation of a month, such as January or March
					s += months[date.getMonth()];
				}
				else if (format == 'M') { // A short textual representation of a month, three letters
					s += months_short[date.getMonth()];
				}
				else if (format == 'm') { // Numeric representation of a month, with leading zeros
					s += (date.getMonth() + 1);
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 'n') { // Numeric representation of a month, without leading zeros
					s += (date.getMonth() + 1);
				}
				else if (format == 'y') { // Year, 2 digits
					s += date.getFullYear().toString().substr(2);
				}
				else if (format == 'Y') { // A full numeric representation of a year, 4 digits
					s += date.getFullYear();
				}
				else if (format == 'a') { // Lowercase Ante meridiem and Post meridiem
					s += (date.getHours() >= 11 && date.getHours() <= 22 ? "pm" : "am");
				}
				else if (format == 'A') { // Uppercase Ante meridiem and Post meridiem
					s += (date.getHours() >= 11 && date.getHours() <= 22 ? "PM" : "AM");
				}
				else if (format == 'g') { // 12-hour format of an hour without leading zeros
					s += (date.getHours() % 12) + 1;
				}
				else if (format == 'h') { // 12-hour format of an hour with leading zeros
					s += (date.getHours() % 12) + 1;
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 'G') { // 24-hour format of an hour without leading zeros
					s += date.getHours();
				}
				else if (format == 'H') { // 24-hour format of an hour with leading zeros
					s += date.getHours();
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 'i') { // Minutes with leading zeros
					s += date.getMinutes();
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 's') { // Seconds with leading zeros
					s += date.getSeconds();
					if (s.length < 2) s = "0" + s;
				}
				else if (format == 'u') { // Microseconds
					s += date.getMilliseconds();
				}
				else { // Unknown
					s += format;
				}
				return s;
			}

			return {

				format: function (timestamp, format) {
					// Based on: http://php.net/manual/en/function.date.php
					var date = new Date(timestamp);

					return format.replace(/(\\*)([a-zA-Z])/g, function (match, esc, fmt) {
						if (esc.length > 0) {
							if ((esc.length % 2) == 1) {
								// Escaped
								return esc.substr(1, (esc.length - 1) / 2) + fmt;
							}
							// Remove slashes
							return esc.substr(0, esc.length / 2) + format_value(date, fmt);
						}
						return format_value(date, fmt);
					});
				}

			};

		})(),

		insert_dom_element_sorted: function (parent, element, sort_function, sort_function_data) {
			// sort_function = function (existing_element, inserting_element, sort_function_data) {}
			// should return true if inserting_element should be inserted after existing_element
			// starts from the end, moves to the start
			var children = parent.children(), i = children.length - 1, c;
			while (i >= 0) {
				// Insert?
				if (sort_function((c = $(children[i])), element, sort_function_data)) {
					c.after(element);
					return;
				}
				// Next
				--i;
			}
			// Add to start
			parent.prepend(element);
		},

		insert_element_sorted: function (array, element, sort_function, sort_function_data) {
			// sort_function = function (existing_element, inserting_element, sort_function_data) {}
			// should return true if inserting_element should be inserted after existing_element
			// starts from the end, moves to the start
			var i = array.length - 1;
			while (i >= 0) {
				// Insert?
				if (sort_function(array[i], element, sort_function_data)) {
					array.splice(i + 1, 0, element);
					return i + 1;
				}
				// Next
				--i;
			}
			// Add to start
			array.splice(0, 0, element);
			return 0;
		},

		choose: function (elements, count) {
			// (elements.length choose count)
			var result = [];
			for (var i = 0; i < elements.length; i++) {
				if (count == 1) {
					result.push([elements[i]]);
				}
				else {
					var more = xch.choose(elements.slice(i+1, elements.length), count - 1);
					for (var j = 0; j < more.length; j++) {
						result.push([elements[i]].concat(more[j]));
					}
				}
			}
			return result;
		},

		swap_elements: function (e1, e2) {
			e1 = e1[0];
			e2 = e2[0];
			var p1 = e1.parentNode;
			var p2 = e2.parentNode;
			if (p1 && p2) {
				var e1_sibling = (e1.nextSibling === e2 ? e1 : e1.nextSibling);
				p2.insertBefore(e1, e2);
				if (e1_sibling) {
					p1.insertBefore(e2, e1_sibling);
				}
				else {
					p1.appendChild(e2);
				}
			}
		},

		get_selection: function (element) {
			var start = 0, end = 0, elem = element[0];

			if (typeof(elem.selectionStart) == "number" && typeof(elem.selectionEnd) == "number") {
				start = elem.selectionStart;
				end = elem.selectionEnd;
			}
			else {
				var range = document.selection.createRange();

				if (range && range.parentElement() == elem) {
					var len = elem.value.length;
					var normalized = elem.value.replace(/\r\n/g, "\n");

					var input_range = elem.createTextRange();
					input_range.moveToBookmark(range.getBookmark());

					var end_range = elem.createTextRange();
					end_range.collapse(false);

					if (input_range.compareEndPoints("StartToEnd", end_range) > -1) {
						start = end = len;
					}
					else {
						start = -input_range.moveStart("character", -len);
						start += normalized.slice(0, start).split("\n").length - 1;

						if (input_range.compareEndPoints("EndToEnd", end_range) > -1) {
							end = len;
						}
						else {
							end = -input_range.moveEnd("character", -len);
							end += normalized.slice(0, end).split("\n").length - 1;
						}
					}
				}
			}

			return {
				start: start,
				end: end
			};
		},
		set_selection: function (element, start, end) {
			element = element[0];

			if (element.createTextRange) {
				var selRange = element.createTextRange();
				selRange.collapse(true);
				selRange.moveStart("character", start);
				selRange.moveEnd("character", end);
				selRange.select();
				element.focus();
			}
			else if (element.setSelectionRange) {
				element.focus();
				element.setSelectionRange(start, end);
			}
			else if (element.selectionStart !== undefined) {
				element.selectionStart = start;
				element.selectionEnd = end;
				element.focus();
			}
		},

		format_number: function (n, cutoff, divisor, decimal_count, labels) {
			var d = Math.pow(10, decimal_count), i = 0;

			while (true) {
				if (n < cutoff || i + 1 >= labels.length) break;
				n /= divisor;
				++i;
			}

			return (Math.round(n * d) / d) + labels[i];
		},

		format_decimal: function (n, max_decimals, min_decimals) {
			var d = Math.pow(10, max_decimals);
			n = n.toFixed(Math.round(max_decimals * d) / d);

			var len = n.length, removed = false;
			min_decimals = min_decimals || 0;
			while (n[--len] == "0" && max_decimals > min_decimals) {
				n = n.substr(0, n.length - 1);
				--max_decimals;
				removed = true;
			}
			if (max_decimals == 0 && removed) n = n.substr(0, n.length - 1);

			return n;
		},

		dom_to_string: function (object, filter_function) {
			var str = "";

			var conts = object.contents();
			for (var i = 0; i < conts.length; ++i) {
				if (conts[i].tagName === undefined) {
					str += conts[i].nodeValue.replace(/\s+/g, " ");
				}
				else {
					var me = $(conts[i]);
					if (!filter_function || filter_function.call(conts[i], me, object)) {
						str += xch.dom_to_string(me);
						if (conts[i].tagName == "BR" || me.css("display") == "block") str += "\n";
					}
				}
			}

			return str;
		},

		string_to_dom: (function () {

			var pfs_supported = ((new DOMParser()).parseFromString("", "text/html") != null);

			return (pfs_supported ?
				function (str) {
					// Firefox version
					var html;
					try {
						html = (new DOMParser()).parseFromString(str, "text/html");
						/*!debug!*/if (!html) console.log("string_to_dom parsing error: " + html + " returned");
						/*!debug!*/if (!html) console.log(str);
					}
					catch (e) {
						html = null;
						/*!debug!*/console.log("string_to_dom parsing error: " + e.toString());
						/*!debug!*/console.log(str);
					}
					return html;
				} :
				function (str) {
					// Chrome version
					var new_document = document.implementation.createHTMLDocument("");
					var doc_element = new_document.documentElement;

					doc_element.innerHTML = str;
					var first_element = doc_element.firstElementChild;

					if (
						doc_element.childElementCount === 1 &&
						first_element.localName.toLowerCase() === "html"
					) {
						new_document.replaceChild(first_element, doc_element);
					}

					return new_document;
				}
			);

		})(),

		get_mime_type_from_extension: function (ext) {
			ext = ext.toLowerCase();

			for (var mt in xch.mime_types) {
				for (var i = 0, exts = xch.mime_types[mt].extensions; i < exts.length; ++i) {
					if (ext == exts[i]) return mt;
				}
			}

			return null;
		},

		ensure_same_mime_type_extension: function (source_filename, target_filename) {
			var ext1 = source_filename.split(".");
			var ext2 = target_filename.split(".");
			ext1 = (ext1.length == 1 ? "" : "." + ext1.pop());
			ext2 = (ext2.length == 1 ? "" : "." + ext2.pop());

			if (xch.get_mime_type_from_extension(ext1) != xch.get_mime_type_from_extension(ext2)) {
				return target_filename + ext1;
			}
			return target_filename;
		},

		get_debug_info: function () {
			// Get saved data
			var sd_keys = xch.list_value_keys();
			sd_keys.sort(function(a, b) {
				return (a < b ? -1 : (a > b ? 1 : 0));
			});
			var sd = {};
			for (var i = 0; i < sd_keys.length; ++i) {
				sd[sd_keys[i]] = xch.get_value(sd_keys[i], null);
			}

			// Data to save
			var data = {
				info: info,
				content: content,
				communication: communication,
				location: location,
				settings: settings,
				saved_data: sd
			};

			var info_str = xch.JSON.stringify(data, function (obj, level, member, parent) {
				if (obj instanceof Function) {
					// Stub
					return false;
				}
				else if (obj instanceof xch.Content) {
					return {
						filter: {
							deny: [{
								name: "board_map",
								cascade: false,
								stub: true
							}, {
								name: "my_posts",
								cascade: false,
								stub: true
							}]
						}
					};
				}
				else if (obj instanceof xch.PostQueue) {
					return {
						filter: {
							deny: [{
								name: "post_queue",
								cacade: false,
								stub: true
							}]
						}
					};
				}
				else if (obj instanceof xch.Thread) {
					// Show nothing
					return false;
				}
				else if (obj instanceof xch.Post) {
					// Show nothing
					return false;
				}
				else if (obj instanceof xch.Image) {
					// Show nothing
					return false;
				}
				else if (obj instanceof xch.Style) {
					// Show nothing
					return false;
				}
				else if (obj instanceof xch.QuickReply) {
					// Show nothing
					return {
						filter: {
							deny: [{
								name: "saved_post_settings",
								cascade: false,
								stub: true
							}, {
								name: "post_data",
								cascade: false,
								stub: true
							}, {
								name: ["post_context","data_fields_extra"],
								cascade: false,
								stub: true
							}, {
								name: ["submitting","ajax"],
								cascade: false,
								stub: true
							}]
						}
					};
				}
				else if (obj instanceof jQuery) {
					// Limit
					return {
						info: {
							type: "jQuery"
						},
						filter: {
							depth: 1,
							allow: [{
								name: "selector",
								cascade: false
							}, {
								name: "length",
								cascade: false
							}],
							inherit: {
								depth: true,
								allow: false,
								deny: false
							}
						}
					};
				}
				else if (obj == sd) {
					// Show nothing for saved data
					return {
						filter: {
							deny: [{
								name: function (name, level_offset) {
									return (name.indexOf("xch") != 0 || name.indexOf("my_posts") >= 0);
								},
								cascade: false,
								stub: false
							}]
						}
					};
				}
				return true;
			});

			return info_str;
		},

		format_string: function (str, formatters, data) {
			if (formatters instanceof Function) {
				// Function call
				return str.replace(/%(\w+|%)\\?/g, function (match, fmt) {
					return formatters.call(match, fmt, data);
				});
			}
			else if (formatters instanceof Object) {
				return str.replace(/%(\w+|%)\\?/g, function (match, fmt) {
					if (fmt in formatters) {
						// Function
						if (formatters[fmt] instanceof Function) {
							return formatters[fmt].call(match, fmt, data);
						}
						// String or other
						return formatters[fmt];
					}

					return fmt;
				});
			}
			else {
				return str.replace(/%(\w+|%)\\?/g, function (match, fmt) {
					return fmt;
				});
			}
		},

		string_to_truth: function (str) {
			str = str.trim().toLowerCase();
			return !(str.length == 0 || str == "0" || str == "no" || str == "false");
		},

		merge_settings: function (source, target, new_objects_allowed) {
			for (var key in source) {
				if (key in target) {
					if (source[key] instanceof Object) {
						if (target[key] instanceof Object) {
							// Recursive
							xch.merge_settings(source[key], target[key], new_objects_allowed);
						}
						else {
							// Object copy
							if (new_objects_allowed) {
								target[key] = source[key];
							}
						}
					}
					else {
						// Simple
						target[key] = source[key];
					}
				}
			}
		},

		regex_array: function (str) {
			var ai = 1;
			var matches = [];

			while (true) {
				var match = arguments[ai].exec(str);
				if (!match) break;
				if (ai + 1 < arguments.length) ++ai;
				for (var i = 1; i < match.length; ++i) {
					if (match[i] != null) {
						matches.push(match[i]);
						break;
					}
				}

				str = str.substr(match.index + match[0].length);
			}

			return matches;
		},

		JSON: (function () {

			var Pointer = function (tree) {
				this.tree = tree;
			};
			var Type = function (typename, data, has_data) {
				this.typename = typename;
				this.data = data;
				this.parse = true;
				this.has_data = has_data;

				if (has_data && typeof(typename) != typeof("")) {
					this.data = typename;
					/*
					if (typename == Function && typeof(this.data) == typeof("")) {
						this.parse = false;
						this.data = new Function(this.data.replace(/^\s*function\s*\w*?\(\s*\)\s*\{|}\s*$/g, ""));
					}
					*/
				}
			};
			var Filter = function () {
				this.depth = -1;
				this.deny = null;
				this.allow = null;
				this.stub = false;

				if (arguments.length == 1) {
					var other = arguments[0];
					if (other.depth >= 0) this.depth = other.depth - 1;
					if (other.deny != null) this.deny = other.deny.slice(0);
					if (other.allow != null) this.allow = other.allow.slice(0);
				}
				else if (arguments.length > 2) {
					var other = arguments[0];
					var source = arguments[1];
					var source_level = arguments[2];

					if (other.depth >= 0) {
						this.depth = other.depth - 1;
					}
					if ("inherit" in source) {
						if (!source.inherit.depth) this.depth = ("depth" in source) ? source.depth : -1;
					}
					else if ("depth" in source) {
						this.depth = source.depth;
					}

					if (other.deny != null && (!("inherit" in source) || source.inherit.deny)) {
						this.deny = other.deny.slice(0);
					}
					if ("deny" in source) {
						if (this.deny == null) this.deny = [];
						for (var i = 0; i < source.deny.length; ++i) {
							this.deny.push(source.deny[i]);
							source.deny[i].source_level = source_level;
							if (!(source.deny[i].name instanceof Array)) source.deny[i].name = [ source.deny[i].name ]; // wrap
						}
					}

					if (other.allow != null && (!("inherit" in source) || source.inherit.allow)) {
						this.allow = other.allow.slice(0);
					}
					if ("allow" in source) {
						if (this.allow == null) this.allow = [];
						for (var i = 0; i < source.allow.length; ++i) {
							this.allow.push(source.allow[i]);
							source.allow[i].source_level = source_level;
							if (!(source.allow[i].name instanceof Array)) source.allow[i].name = [ source.allow[i].name ]; // wrap
						}
					}

					if ("stub" in source) this.stub = source.stub;
				}
			};

			var class_of = function (obj) {
				var n = "";
				if (obj.constructor) {
					n = obj.constructor.name;
				}
				if (!n) {
					n = "Object";
				}
				return n;
			};
			var string_representation = function (str) {
				return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t") + '"';
			};

			var needs_stringed_key = function (key) {
				key = key.toString();
				return (/\W/.test(key) || /^[0-9]+[^0-9]/.test(key));
			};

			var string_settings = {
				object_opener: "{",
				object_closer: "}",
				array_opener: "[",
				array_closer: "]",
				name_delimiter: ": ",
				list_delimiter: ",",
				newline: "\n",
				spacing: "  ",
				pointer_function: "xch.JSON.pointer",
				type_function: "xch.JSON.type",
				stub_function: "xch.JSON.stub",
				function_start: "(",
				function_end: ")",
				function_param_delimiter: ", ",
				filtered_members: "/* ... */",
				stub_function_body: "function () { /* ... */ }"
			};

			var check_filter = function (data, member, level, filter, location) {
				var good = true, stub = false, i, j, k, cont;

				// Allowed
				if (filter.allow != null) {
					good = false;
					for (i = 0; i < filter.allow.length; ++i) {
						// Level okay
						if (
							location.length >= filter.allow[i].name.length &&
							(filter.allow[i].cascade || level == filter.allow[i].source_level + filter.allow[i].name.length)
						) {
							// Name check
							j = filter.allow[i].name.length - 1;
							// Compare
							if (filter.allow[i].name[j] instanceof Function) {
								cont = filter.allow[i].name[j].call(data, member, 0);
							}
							else {
								cont = (member === filter.allow[i].name[j]);
							}
							// Check
							if (cont) {
								k = location.length - 1;
								for (--j; j >= 0; --j) {
									// Compare
									if (filter.allow[i].name[j] instanceof Function) {
										cont = filter.allow[i].name[j].call(data, location[k], location.length - k);
									}
									else {
										cont = (location[k] === filter.allow[i].name[j]);
									}
									// Check
									if (!cont) break; // mismatch
									--k;
								}
								// Complete match
								if (j < 0) {
									good = true;
									break;
								}
							}
						}
					}
					if (!good) {
						stub = filter.stub || false;
					}
				}

				// Deny
				if (good && filter.deny != null) {
					for (i = 0; i < filter.deny.length; ++i) {
						// Level okay
						if (
							location.length >= filter.deny[i].name.length &&
							(filter.deny[i].cascade || level == filter.deny[i].source_level + filter.deny[i].name.length)
						) {
							// Name check
							j = filter.deny[i].name.length - 1;
							// Compare
							if (filter.deny[i].name[j] instanceof Function) {
								cont = filter.deny[i].name[j].call(data, member, 0);
							}
							else {
								cont = (member === filter.deny[i].name[j]);
							}
							// Check
							if (cont) {
								k = location.length - 1;
								for (--j; j >= 0; --j) {
									// Compare
									if (filter.deny[i].name[j] instanceof Function) {
										cont = filter.deny[i].name[j].call(data, location[k], location.length - k);
									}
									else {
										cont = (location[k] === filter.deny[i].name[j]);
									}
									// Check
									if (!cont) break; // mismatch
									--k;
								}
								// Complete match
								if (j < 0) {
									good = false;
									stub = filter.deny[i].stub || false;
									break;
								}
							}
						}
					}
				}

				// Done
				return [ good , stub ];
			};
			var form_object = function (data, root) {
				// Corrects all filtered/cyclic/typed references

				if (data instanceof Object) {
					for (var member in data) {
						// Type checking
						if (data[member] instanceof Pointer) {
							// Resolve pointer
							var r = root;
							for (var i = 0, tree = data[member].tree; i < tree.length; ++i) {
								if (tree[i] in r) {
									r = r[tree[i]];
								}
								else {
									r = undefined;
									break;
								}
							}
							data[member] = r;
						}
						else if (data[member] instanceof Type) {
							if (data[member].has_data) {
								// Remove type
								data[member] = (data[member].parse ? form_object(data[member].data) : data[member].data);
							}
							else {
								// Filtered; remove
								delete data[member];
							}
						}
						else {
							// Continue filtering
							data[member] = form_object(data[member]);
						}
					}
				}

				return data;
			};
			var form_string = function (data, about_function, info, stub, member_name, parent) {
				if (info == null) {
					info = {
						parsed: [],
						filter: new Filter(),
						spacing: "",
						location: ["root"]
					};
				}

				var str = "";

				if (data instanceof Object) {
					// Pointer?
					var found = false;
					for (var i = 0; i < info.parsed.length; ++i) {
						if (info.parsed[i].data == data) {
							// Create pointer
							str += string_settings.pointer_function + string_settings.function_start;
							for (var j = 0; j < info.parsed[i].location.length; ++j) {
								if (j > 0) str += string_settings.function_param_delimiter;
								if (typeof(info.parsed[i].location[j]) == typeof(0)) {
									str += info.parsed[i].location[j];
								}
								else {
									str += string_representation(info.parsed[i].location[j]);
								}
							}
							str += string_settings.function_end;
							found = true;
							break;
						}
					}

					// Not a pointer
					if (!found) {
						// Get the prototype (if any)
						var proto = data.__proto__ || (data.constructor ? data.constructor.prototype : null);

						// Add to parsed
						info.parsed.push({
							data: data,
							location: info.location.slice(0)
						});

						// About info
						var level = info.location.length - 1;
						var about = (about_function ? about_function(data, level, member_name, parent) : true);

						// Vars
						var type = class_of(data);
						var old_filter = info.filter;

						// New filter
						if (about instanceof Object) {
							// Info
							if ("info" in about) {
								if ("type" in about.info) type = about.info.type;
							}
							// New filter
							if ("filter" in about) {
								if (about.filter instanceof Object) {
									info.filter = new Filter(old_filter, about.filter, level);
								}
							}
						}
						if (info.filter == old_filter) {
							// New filter
							info.filter = new Filter(old_filter);
						}

						// Display as stub
						stub = stub || (about === false || (about instanceof Object && "filter" in about && about.filter === false));

						// What type
						if (data instanceof Function) {
							// Function
							str += string_settings.type_function + string_settings.function_start + "Function" + string_settings.function_param_delimiter;
							str += string_representation(stub ? string_settings.stub_function_body : data.toString());
							str += string_settings.function_end;
						}
						else {
							// Stub
							if (stub) {
								// Fully filtered out
								str += string_settings.stub_function + string_settings.function_start;
								str += string_representation(type);
								str += string_settings.function_end;
							}
							else {
								var is_array = (data instanceof Array);

								// Spacing change
								info.spacing += string_settings.spacing;

								// Typed function
								if (!is_array && type != "Object") {
									str += string_settings.type_function + string_settings.function_start;
									str += string_representation(type);
									str += string_settings.function_param_delimiter;
								}

								// Opener
								str += (is_array ? string_settings.array_opener : string_settings.object_opener);

								// Members/array values
								var not_first = false;
								var missing_count = 0;
								if (info.filter.depth == 0) {
									// Display no members
									missing_count = 1;
								}
								else {
									for (var member in data) {
										// Not a prototype member
										if (is_array || !(proto && (member in proto) && proto[member] === data[member])) {
											// Filter
											if (is_array) member = parseInt(member);
											var good = check_filter(data, member, level + 1, info.filter, info.location);

											// Insert member
											if (good[0] || good[1]) {
												// Update location
												info.location.push(member);

												// String update
												if (not_first) str += string_settings.list_delimiter;
												str += string_settings.newline + info.spacing;
												if (!is_array) str += (needs_stringed_key(member) ? string_representation(member.toString()) : member) + string_settings.name_delimiter;
												try {
													str += form_string(data[member], about_function, info, good[1], member, data);
												}
												catch (e) {
													str += form_string({}, about_function, info, good[1], "", null);
												}

												// Next
												not_first = true;
												info.location.pop();
											}
											else {
												// Omitted
												++missing_count;
											}
										}
									}
								}
								// Anything missing
								if (missing_count > 0) {
									str += string_settings.newline + info.spacing + string_settings.filtered_members;
								}

								// Finish
								info.spacing = info.spacing.substr(0, info.spacing.length - string_settings.spacing.length);
								str += string_settings.newline + info.spacing;
								str += (is_array ? string_settings.array_closer : string_settings.object_closer);

								// Function finish
								if (!is_array && type != "Object") {
									str += string_settings.function_end;
								}
							}
						}

						// Filter change
						info.filter = old_filter;
					}
				}
				else {
					if (stub) {
						// Fully filtered out
						str += string_settings.stub_function + string_settings.function_start;
						if (data === true || data === false) {
							str += string_representation("boolean");
						}
						else if (data === null) {
							str += string_representation("null");
						}
						else if (typeof(data) == typeof(1)) {
							str += string_representation("number");
						}
						else if (typeof(data) == typeof("")) {
							str += string_representation("string");
						}
						else {
							str += string_representation("undefined");
						}
						str += string_settings.function_end;
					}
					else {
						// Simple types
						if (data === true) {
							str += "true";
						}
						else if (data === false) {
							str += "false";
						}
						else if (data === null) {
							str += "null";
						}
						else if (typeof(data) == typeof(1)) {
							str += data.toString();
						}
						else if (typeof(data) == typeof("")) {
							str += string_representation(data);
						}
						else {
							str += "undefined";
						}
					}
				}

				return str;
			};

			// Methods
			return {

				type: function (typename, data) {
					return new Type(typename, data, arguments.length >= 2);
				},
				stub: function (typename) {
					return new Type(typename, null, false);
				},
				pointer: function () {
					return new Pointer(Array.prototype.slice.call(arguments, 0));
				},
				object: function (typename, data) {
					return form_object(data, data);
				},
				stringify: function (data, about_function) {
					return form_string(data, about_function, null, false, "", null);
				}

			};

		})(),


		// Updater
		ScriptUpdater: (function () {

			var ScriptUpdater = function () {
				this.error = false;
				this.changelog = null;
				this.ajax = null;

				this.settings = {
					check_interval: 60 * 60 // 1 hour
				};
			};

			var this_private = {

				on_ajax_load: function (event) {
					var j_data = null;
					try {
						j_data = JSON.parse(event.response);
					}
					catch (e) {
						j_data = null;
					}
					if (j_data != null) {
						event.data.self.changelog = event.data.self.parse_changelog(j_data);
						event.data.self.check_changelog_for_updates(event.data.self.changelog, event.data.update_data);
					}
					else {
						event.data.self.error = true;
					}
					event.data.self.ajax = null;
				},
				on_ajax_error: function (event) {
					event.data.self.error = true;
					event.data.self.ajax = null;
				},
				on_ajax_abort: function (event) {
					event.data.self.error = true;
					event.data.self.ajax = null;
				},
				on_ajax_progress: function (event) {
				},

				title_is_relevant: function (title) {
					return /[0-9\.]/.test(title);
				},

				version_compare: function (version_old, version_current) {
					if (version_current == version_old) return -2; // Same

					var v_current = version_current.split(".");
					var v_old = version_old.split(".");
					var change_direction = 0;

					var i, cur, old, cur_n, old_n, len = v_current.length;
					if (v_old.length > len) len = v_old.length;
					for (i = 0; i < len; ++i) {
						cur = (i < v_current.length ? parseInt(v_current[i].trim(), 10) : 0);
						old = (i < v_old.length ? parseInt(v_old[i].trim(), 10) : 0);
						cur = ((cur_n = isFinite(cur)) ? cur : 0);
						old = ((old_n = isFinite(old)) ? old : 0);
						if (cur == old) {
							if (!cur_n || !old_n) change_direction = 0;
						}
						else {
							if (cur > old) {
								// Newer
								change_direction = 1;
							}
							else {
								// Older
								change_direction = -1;
							}
							break;
						}
					}

					return change_direction;
				},

				fresh_install_alert: function (version_current) {
					new xch.Message({
						type: "good",
						title: "Userscript Installed",
						html: (
							style.e("div")
							.append(
								style.e("div")
								.append(style.t("Thank you for installing "))
								.append(
									style.e("a", "message_link")
									.attr("href", xch.script.homepage)
									.attr("target", "_blank")
									.text(xch.script.name)
								)
								.append(style.t("!"))
							)
							.append(
								style.e("div")
								.text("Please note that it is not complete yet, and some features will be missing.")
							)
						),
						life: 0
					});
				},
				version_change_alert: function (version_current, version_old) {
					var change_direction = this_private.version_compare.call(this, version_old, version_current);

					// Change
					if (change_direction > -2) {
						// Form text/links
						var change_text = "Script version has ";
						var changelog_link = "#!changes?from=";
						if (change_direction == 0) {
							change_text += "changed";
							changelog_link += version_old + "&to=" + version_current;
						}
						else if (change_direction > 0) {
							change_text += "increased";
							changelog_link += version_old + "&to=" + version_current;
						}
						else {
							change_text += "decreased";
							changelog_link += version_current + "&to=" + version_old;
						}
						change_text += " from ";

						// Html
						var message, changes_container, changes_list;
						(message = style.e("div"))
						.append(
							style.e("div")
							.append(
								style.t(change_text)
							)
							.append(
								style.e("a", "message_link")
								.attr("href", xch.script.homepage + "#!changes?version=" + version_old)
								.attr("target", "_blank")
								.text(version_old)
							)
							.append(style.t(" to "))
							.append(
								style.e("a", "message_link")
								.attr("href", xch.script.homepage + changelog_link)
								.attr("target", "_blank")
								.text(version_current)
							)
							.append(style.t("!"))
						);

						// Message
						new xch.Message({
							type: "good",
							title: "Userscript Updated",
							html: message,
							life: 0
						});
					}
				},

				update_alert: function (changes) {
					var message, show_changes, changes_container, changes_list;
					(message = style.e("div"))
					.append(
						style.e("div")
						.append(
							style.e("a", "message_link")
							.text("Click here")
							.attr("target", "_blank")
							.attr("href", xch.script.browser.chrome ? xch.script.update_homepage : xch.script.update_url)
						)
						.append(style.t(" to update, "))
						.append(
							(show_changes = style.e("a", "message_link"))
							.text("show changes")
						)
						.append(style.t(", or "))
						.append(
							style.e("a", "message_link")
							.text("view source")
							.attr("target", "_blank")
							.attr("href", "https://github.com/dnsev/xch/commit/" + changes[0].sha)
						)
						.append(style.t("."))
					)
					.append(
						(changes_container = style.e("div", "message_changes hidden"))
						.append(
							(changes_list = style.e("ul", "message_list"))
						)
					);

					// Events
					show_changes.on("click", { toggle_container: changes_container }, this_private.on_update_alert_changes_toggle_click);

					// Form changes
					for (var i = 0; i < changes.length; ++i) {
						var c = this.get_individual_changes(changes[i]);
						for (var j = 0; j < c.length; ++j) {
							changes_list.append(
								style.e("li", "message_list_item" + (j == 0 ? " message_list_item_big_spacing" : ""))
								.text(c[j])
							);
						}
					}

					new xch.Message({
						type: "good",
						title: "Update Available: Version " + changes[0].title,
						html: message,
						life: 0
					});
				},

				on_update_alert_changes_toggle_click: function (event) {
					if (event.which != 1) return true;

					if (event.data.toggle_container.hasClass("hidden")) {
						event.data.toggle_container.removeClass("hidden");
						$(this).text("hide changes");
					}
					else {
						event.data.toggle_container.addClass("hidden");
						$(this).text("show changes");
					}
				}

			};

			ScriptUpdater.prototype = {

				constructor: ScriptUpdater,

				execute: function () {
					this.update_check(this.version_check());
				},

				update_check: function (forced) {
					var check = false;
					var now = (new Date()).getTime();

					var update = (forced ? null : xch.site_get_value("script_update", null));
					if (update === null) {
						check = true;
						update = {
							timestamp: now,
							version: xch.script.version.trim()
						};
					}
					else {
						var timestamp_diff = (now - update.timestamp) || 0;

						if (timestamp_diff >= this.settings.check_interval * 1000) {
							// Full check
							check = true;
							update.timestamp = now;
							update.version = xch.script.version.trim();
						}
					}

					if (check) {
						// Check
						this.ajax = xch.ajax({
							url: "https://api.github.com/repos/dnsev/xch/commits",
							method: "GET",
							return_type: "text",
							on: {
								load: this_private.on_ajax_load,
								error: this_private.on_ajax_error,
								abort: this_private.on_ajax_abort,
								progress: this_private.on_ajax_progress
							},
							self: this,
							update_data: update
						});
					}
				},

				parse_changelog: function (j_data) {
					var changelog = [];

					for (var i = 0; i < j_data.length; ++i) {
						var title = j_data[i].commit.message.replace(/\s*\n\s*(0|[^0])*$/, "");

						if (this_private.title_is_relevant.call(this, title)) {
							var entry = {
								sha: j_data[i].sha,
								title: title,
								comment: j_data[i].commit.message.replace(/^[^\r\n]*\r?\n?\r?\n?/, ""),
								timestamp: 0,
							};

							var date = /^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+)Z$/.exec(j_data[i].commit.committer.date);
							if (date) {
								entry.timestamp = (new Date(
									parseInt(date[1]),
									parseInt(date[2]) - 1,
									parseInt(date[3]),
									parseInt(date[4]),
									parseInt(date[5]),
									parseInt(date[6])
								)).getTime();
							}

							changelog.push(entry);
						}
					}

					return changelog;
				},
				check_changelog_for_updates: function (changelog, update_data) {
					if (update_data.version == "inline") update_data.version = update_data.version_old || update_data.version;

					var version_current = update_data.version;
					var changes = this.get_changelog_updates(changelog, version_current);

					if (changes.length > 0) {
						update_data.version = changes[0].title;
						this_private.update_alert.call(this, changes);
					}

					// Save
					xch.site_set_value("script_update", update_data);
				},
				get_changelog_updates: function (changelog, from_version) {
					var changes = [];

					for (var i = 0; i < changelog.length; ++i) {
						var change_direction = this_private.version_compare.call(this, from_version, changelog[i].title);

						if (change_direction >= 0) {
							changes.push(changelog[i]);
						}
					}

					return changes;
				},
				get_individual_changes: function (entry) {
					// Changes list
					var changes = entry.comment.split("\n");
					// Fix back into a single line if necessary
					for (var i = 1; i < changes.length; ++i) {
						if (/^[a-z]/.test(changes[i])) {
							changes[i - 1] = changes[i - 1].trim() + " " + changes[i].trim();
							changes.splice(i, 1);
							--i;
						}
					}
					// Done
					return changes;
				},

				version_check: function () {
					var version_current = xch.script.version.trim();
					var version_old = xch.site_get_value("script_version", null);
					if (version_old === null) {
						// Fresh install
						this_private.fresh_install_alert.call(this, version_current);

						// Change
						xch.site_set_value("script_version", version_current);
						return true;
					}
					else {
						if (version_current != version_old) {
							// Compare
							this_private.version_change_alert.call(this, version_current, version_old);

							// Change
							xch.site_set_value("script_version", version_current);
							return true;
						}
					}
					return false;
				}

			};

			return ScriptUpdater;

		})(),

		// Settings/editor
		Settings: (function () {

			var Settings = function () {

				this_private.setup.call(this);

				// Load
				this_private.load_all.call(this);

				// Values
				this.data = {
					popup: null,

					events: {
						close: {
							triggering: false,
							callbacks: [],
							removals: null
						}
					},

					tabs: [],
					tab_current: 0,
					container: null,
					mode_changer: null,
					mode_menu: null,
					mode: Settings.modes.NORMAL_AND_BELOW,

					target_objects: null
				};

				// Events
				communication.cross_tab_on("settings_save", this_private.on_cross_tab_save, this);

			};

			var apply_changes = {

				to_footer_backlinks: function () {
					for (var i = 0, j; i < content.threads.length; ++i) {
						for (j = 0; j < content.threads[i].posts.length; ++j) {
							content.threads[i].posts[j].update_html_backlink_location(settings.post.footer_backlinks);
						}
					}
				}

			};

			var save = {
				image_preview: function () {
					xch.site_set_value("settings_image_preview", this.image_preview);

					communication.cross_tab_trigger("settings_save", {
						value: "image_preview"
					});
				},
				sourcing: function () {
					xch.site_set_value("settings_sourcing", this.sourcing);

					communication.cross_tab_trigger("settings_save", {
						value: "sourcing"
					});
				},
				post: function () {
					xch.site_set_value("settings_post", this.post);

					communication.cross_tab_trigger("settings_save", {
						value: "post"
					});
				},
				popup: function () {
					xch.site_set_value("settings_popup", this.popup);

					communication.cross_tab_trigger("settings_save", {
						value: "popup"
					});
				}
			};
			var load = {
				image_preview: function (apply) {
					var set = xch.site_get_value("settings_image_preview", this.image_preview);
					if (set !== this.image_preview) {
						// Merge
						xch.merge_settings(set, this.image_preview, false);

						if (apply) {
							// ...
						}
					}
				},
				sourcing: function (apply) {
					var set = xch.site_get_value("settings_sourcing", this.sourcing);
					if (set !== this.sourcing) {
						// Merge
						xch.merge_settings(set, this.sourcing, false);

						if (apply) {
							// ...
						}
					}
				},
				post: function (apply) {
					var set = xch.site_get_value("settings_post", this.post);
					if (set === this.post) {
						// Defaults
						this.post.time.offset = -(new Date()).getTimezoneOffset() * 60 * 1000;
					}
					else {
						// Merge
						xch.merge_settings(set, this.post, false);

						if (apply) {
							// ...
						}
					}
				},
				popup: function (apply) {
					var set = xch.site_get_value("settings_popup", this.popup);
					if (set !== this.popup) {
						// Merge
						xch.merge_settings(set, this.popup, false);

						if (apply) {
							// ...
						}
					}
				}
			};

			var this_private = {

				setup: function () {

					this.image_preview = {
						"default": ".jpg",
						enabled: {
							".jpg": true,
							".png": true,
							".gif": true,
							".bmp": true,
							spoiler: true
						},
						open_timeout: 0.0,
						open_timeout_spoiler: 0.25,
						zooming: {
							enabled: true,
							bounded: true,
							invert: false
						},
						upscale: false,
						click: {
							if_closed: "toggle", // toggle,tab,embed,nothing
							if_open: "embed",
							if_preview: "toggle"
						}
					};
					this.sourcing = {
						source_text: "https://www.google.com/searchbyimage?image_url=%thumbnail;text:google;if_spoiler:false\n> https://www.google.com/searchbyimage?image_url=%thumbnail;text:Thumbnail\n> https://www.google.com/searchbyimage?image_url=%image;text:Full image\nhttps://www.google.com/searchbyimage?image_url=%image;text:google;if_not_spoiler:false\nhttp://regex.info/exif.cgi?imgurl=%image;text:exif"
					};
					this.post = {
						footer_backlinks: false,
						time: {
							format: "m/d/y (D) H:i:s",
							offset: 0
						},
						fetching: {
							new_priority: true,
							parsing_group: {
								size: 20,
								interval: 0.0625,
								use_single: true
							}
						},
						inlining: {
							highlight_on_hover: {
								if_floating: {
									target: true,
									original: true
								},
								if_embedded: {
									target: true,
									original: false
								}
							},
							on_screen: {
								none: {
									inline: true,
									embed: true,
									scroll: true,
									highlight: true
								},
								partial: {
									inline: true,
									embed: true,
									scroll: true,
									highlight: true
								},
								complete: {
									inline: true,
									embed: true,
									scroll: true,
									highlight: true
								}
							},
							allow_cyclic: false
						}
					};
					this.popup = {
						resize_display_time: 0.25,
						anchor_border_size: 10
					};

				},

				get_target_objects: function () {
					var s_t = {
						settings: {
							object: this,
							save_function: Settings.prototype.get_change_apply_functions
						},
						updater: {
							object: content.updater,
							save_function: xch.Updater.prototype.get_change_apply_functions
						}
					};

					return s_t;
				},
				split_target: function (target_string) {
					return xch.regex_array(target_string, /^([a-zA-Z_]\w*)/, /^(?:\.([a-zA-Z_]\w*)|\[([^\]]*)\])/);
				},
				get_target_data: function (t_array, target_objects) {
					for (var key in target_objects) {
						var k_array = this_private.split_target.call(this, key), i;
						if (k_array.length < t_array.length) {
							for (i = 0; i < k_array.length; ++i) {
								if (t_array[i] != k_array[i]) break;
							}
							if (i == k_array.length) {
								return {
									target_data: target_objects[key],
									offset: i
								};
							}
						}
					}

					return {
						target_data: null,
						offset: 0
					};
				},
				get_value: function (target_string, target_objects) {
					var t_array = this_private.split_target.call(this, target_string);

					var t_data = this_private.get_target_data.call(this, t_array, target_objects);
					if (t_data.target_data != null) {
						// Found
						var i = t_data.offset;
						t_data = t_data.target_data;

						// Get
						var val = t_data.object;
						for (; i < t_array.length; ++i) {
							val = val[t_array[i]];
						}
						return val;
					}

					return undefined;
				},
				set_value: function (target_string, value, target_objects) {
					var t_array = this_private.split_target.call(this, target_string);

					var t_data = this_private.get_target_data.call(this, t_array, target_objects);
					if (t_data.target_data != null) {
						// Found
						var i = t_data.offset;
						t_data = t_data.target_data;

						// Get
						var val = t_data.object;
						for (; i < t_array.length - 1; ++i) {
							val = val[t_array[i]];
						}
						val[t_array[i]] = value;
					}
				},

				singular: function (str) {
					return str.substr(0, str.length - 1);
				},

				param_value_to_string: function (params, string_rep) {
					var value = params.current_value;

					if ("adjust_pre" in params.value) {
						value = params.value.adjust_pre.call(this, value);
					}
					if (string_rep) {
						if ("custom" in params.value && value.toString() in params.value.custom) {
							value = params.value.custom[value.toString()];
						}
						else if ("units" in params.value) {
							value += " " + (value == 1 ? this_private.singular.call(this, params.value.units) : params.value.units);
						}
					}

					return value;
				},
				param_value_from_string: function (params, value) {
					// Units removal
					if ("units" in params.value) {
						var units = params.value.units;
						var single = this_private.singular.call(this, units);
						units = units.replace(/[^\w\s]/g, "\\$&");
						single = single.replace(/[^\w\s]/g, "\\$&");

						var re = new RegExp("\s*" + units + "\s*$|\s*" + single + "\s*$", "i");

						// Remove units
						value = value.replace(re, "");
					}

					// Type convert
					var is_int;
					if (params.value.type == "boolean") {
						value = xch.string_to_truth(value.trim());
					}
					else if ((is_int = (params.value.type == "integer")) || params.value.type == "number") {
						value = value.trim();

						// Custom
						if ("custom" in params.value) {
							value = value.toLowerCase();
							for (var c in params.value.custom) {
								if (params.value.custom[c].toLowerCase() == value) {
									value = c;
									break;
								}
							}
						}

						// Value
						value = parseFloat(value);
						if (isFinite(value)) {
							if (is_int) value = Math.round(value);
						}
						else {
							value = 0;
						}

						// Bounding
						if ("min" in params.value) {
							if (value < params.value.min) value = params.value.min;
						}
						if ("max" in params.value) {
							if (value > params.value.max) value = params.value.max;
						}
					}

					// Post modify
					if ("adjust_post" in params.value) {
						value = params.value.adjust_post.call(this, value);
					}

					// Done
					return value;
				},

				trigger: function (event, data) {
					// Trigger an event
					var e = this.data.events[event];
					e.triggering = true;
					for (var i = 0, j = e.callbacks.length; i < j; ++i) {
						e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
					}
					e.triggering = false;
					if (e.removals != null) {
						for (var i = 0; i < e.removals.length; ++i) {
							this.off(event, e.removals[i]);
						}
						e.removals = null;
					}
				},

				create_item: function (params, data) {
					// Create
					var e = null;

					// Title and description
					if ("label" in params) {
						if (e == null) e = style.e("div", "settings_section_item");
						e.append(
							style.e("div", "settings_section_item_label")
							.text(params.label)
						);
					}
					if ("description" in params) {
						if (e == null) e = style.e("div", "settings_section_item");
						var d;
						e.append(
							(d = style.e("div", "settings_section_item_description"))
						);
						if (typeof(params.description) == typeof("")) {
							d.text(params.description);
						}
						else if (params.description instanceof Array) {
							for (var i = 0; i < params.description.length; ++i) {
								if (i > 0) d.append(style.e("br"));
								d.append(style.t(params.description[i]));
							}
						}
						else {
							d.html(params.description);
						}
					}
					if (e == null && !("pre" in data)) {
						e = style.e("div", "settings_section_item");
					}

					// Value
					var v_after = false;
					var v = null;
					var val = params.value;
					var value;
					var event_data = { self: this, params: params };

					if (val.select == "checkbox") {
						if (val.type == "boolean") {
							(v = style.checkbox(false, "normal"))
							.addClass("settings_value_checkbox_container")
							.append(
								style.e("span", "settings_value_checkbox_display")
								.attr("value_on", val.on || "Enabled")
								.attr("value_off", val.off || "Disabled")
							);

							// Value
							value = this_private.param_value_to_string.call(this, params, false);
							var input = v.find("input");
							input.prop("checked", (val.invert ? !value : value));

							// Event
							input.on("change", event_data, this_private.on_input_checkbox_change);
						}
					}
					else if (val.select == "radio") {
						if (val.type == "boolean") {
							var r1, r2, d;

							(v = style.e("div", "settings_value_radio_select_container"))
							.append(
								style.e("div", "settings_value_radio_select_item")
								.append(
									(r1 = style.radiobox(false, "normal"))
									.addClass("settings_value_radiobox_container")
									.append(
										style.e("span", "settings_value_radiobox_display")
										.attr("value_display", val.on || "Enabled")
									)
								)
							);
							(d = style.e("div", "settings_value_radio_select_item"))
							.append(
								(r2 = style.radiobox(false, "normal"))
								.addClass("settings_value_radiobox_container")
								.append(
									style.e("span", "settings_value_radiobox_display")
									.attr("value_display", val.off || "Disabled")
								)
							);
							if ("reverse" in val && val.reverse) v.prepend(d);
							else v.append(d);

							if (!("single_line" in val) || val.single_line) v.addClass("single_line");

							// Set value
							value = this_private.param_value_to_string.call(this, params, false);
							(value ? r1 : r2).find("input").prop("checked", true);

							// Events
							event_data.inputs = [{
								input: r1.find("input"),
								value: true
							}, {
								input: r2.find("input"),
								value: false
							}];

							r1.find("input").add(r2.find("input"))
							.on("change", event_data, this_private.on_input_radiobox_change);
						}
					}
					else if (val.select == "slider") {
						if (val.type == "integer" || val.type == "number") {
							var val_display, grabber, slider;
							(v = style.e("div", "settings_value_slider"))
							.append(
								(slider = style.e("div", "settings_value_slider_inner_1"))
								.append(
									style.e("div", "settings_value_slider_inner_2")
									.append(
										(grabber = style.e("div", "settings_value_slider_grabber"))
									)
								)
							)
							.append(
								(val_display = style.e("div", "settings_value_slider_value_display"))
							);

							event_data.value_display = val_display;
							event_data.grabber = grabber;

							// Events
							slider
							.on("mousedown", event_data, this_private.on_input_slider_mousedown);

							grabber
							.on("mousedown", event_data, this_private.on_input_slider_grabber_mousedown);

							// Value
							value = this_private.param_value_to_string.call(this, params, false);

							var d_max = ("decimals" in val ? val.decimals : 3);
							var d_min = ("decimals_min" in val ? val.decimals_min : 0);
							val_display.attr("display_value", xch.format_decimal(value, d_max, d_min));

							if ("units" in val) {
								val_display.attr("display_units", value == 1 ? this_private.singular.call(this, val.units) : val.units);
							}

							grabber.css("left", ((value - val.min) / (val.max - val.min) * 100).toFixed(2) + "%");
						}
					}
					else if (val.select == "textbox") {
						if (val.type == "integer" || val.type == "number" || val.type == "string") {
							(v = style.e("input", "popup_input settings_value_input"))
							.attr("type", "text");

							v.on("focus", event_data, this_private.on_input_textbox_focus);
							v.on("blur", event_data, this_private.on_input_textbox_blur);

							// Value
							value = this_private.param_value_to_string.call(this, params, true);
							v.val(value);
						}
					}
					else if (val.select == "textarea") {
						if (val.type == "integer" || val.type == "number" || val.type == "string") {
							v_after = true;
							(v = style.e("textarea", "popup_input settings_value_textarea_input"));
							if ("height" in val) {
								v.css("height", (val.height).toFixed(2) + "px");
							}

							v.on("focus", event_data, this_private.on_input_textbox_focus);
							v.on("blur", event_data, this_private.on_input_textbox_blur);

							// Value
							value = this_private.param_value_to_string.call(this, params, true);
							v.val(value);
						}
					}
					else if (val.select == "menu") {
						if (val.type == "enum") {
							(v = style.e("div", "settings_value_text_display"));
							v.on("click", event_data, this_private.on_input_menu_click);

							// Value
							value = this_private.param_value_to_string.call(this, params, false);
							var i;
							for (i = 0; i < val.options.length; ++i) {
								if (val.options[i].value == value) {
									v.attr("display_value", val.options[i].label);
									break;
								}
							}
							if (i == val.options.length) {
								v.attr("display_value", value);
							}
						}
					}

					// Add
					if (v != null) {
						// Wrap
						var v2 = style.e("div", "settings_value_item");
						v2.append(v);
						v = v2;
					}
					if (e != null) {
						if (v != null) {
							if (v_after) {
								e.append(
									style.e("div", "settings_value_container full")
									.append(v)
								);
							}
							else {
								e.prepend(
									style.e("div", "settings_value_container")
									.append(v)
								);
							}
						}
						// Update pre
						params.container = e;
						data.pre = e;
					}
					else {
						if (v != null) {
							// Add to previous
							data.pre.addClass("multiple");
							if (v_after) {
								var c = (data.pre.find(".settings_value_container.full"));
								if (c.length == 0) {
									data.pre.append(
										(c = style.e("div", "settings_value_container full"))
									);
								}
								c.append(v);
							}
							else {
								var c = (data.pre.find(".settings_value_container:not(.full)"));
								if (c.length == 0) {
									data.pre.prepend(
										(c = style.e("div", "settings_value_container"))
									);
								}
								c.append(v);
							}
						}
						params.parent_container = data.pre;
					}

					// Done
					return e;
				},

				set_mode: function (mode) {
					// Change text
					this.data.mode = mode;
					this_private.set_mode_text.call(this);

					// Change display
					var tab_first = true, tab_count = 0, section_first, section_count, option_first, option_count, reselect_tab = false;
					for (var i = 0, j, k, sec, item; i < this.data.tabs.length; ++i) {
						// Content
						section_first = true;
						section_count = 0;
						for (j = 0; j < this.data.tabs[i].sections.length; ++j) {
							sec = this.data.tabs[i].sections[j];

							option_first = true;
							option_count = 0;
							for (k = 0; k < sec.contents.length; ++k) {
								// Item
								item = sec.contents[k].container;
								if (item != null) {
									// Item
									if ((sec.contents[k].mode & this.data.mode) != 0) {
										if (option_first) {
											item.addClass("first");
											option_first = false;
										}
										else {
											item.removeClass("first");
										}
										if ((++option_count % 2) == 1) {
											item.addClass("odd");
										}
										else {
											item.removeClass("odd");
										}
										item.removeClass("hidden");
									}
									else {
										item.addClass("hidden").removeClass("first odd");
									}
								}
							}

							// Section
							if (option_count > 0) {
								if (section_first) {
									sec.container.addClass("first");
									section_first = false;
								}
								else {
									sec.container.removeClass("first");
								}
								if ((++section_count % 2) == 1) {
									sec.container.addClass("odd");
								}
								else {
									sec.container.removeClass("odd");
								}
								sec.container.removeClass("hidden");
							}
							else {
								sec.container.addClass("hidden").removeClass("first odd");
							}
						}

						// Tab
						if (section_count > 0) {
							if (tab_first) {
								this.data.tabs[i].container.addClass("first");
								tab_first = false;
							}
							else {
								this.data.tabs[i].container.removeClass("first");
							}
							if ((++tab_count % 2) == 1) {
								this.data.tabs[i].container.addClass("odd");
							}
							else {
								this.data.tabs[i].container.removeClass("odd");
							}
							this.data.tabs[i].container.removeClass("hidden");
						}
						else {
							this.data.tabs[i].container.addClass("hidden").removeClass("first odd");
							if (this.data.tab_current == i) {
								reselect_tab = true;
							}
						}
					}

					// Tab change
					if (reselect_tab) {
						this.data.tabs[this.data.tab_current].container.removeClass("current");
						this.data.tabs[this.data.tab_current].content.addClass("hidden");
						for (this.data.tab_current = 0; this.data.tab_current < this.data.tabs.length; ++this.data.tab_current) {
							if (!this.data.tabs[this.data.tab_current].container.hasClass("hidden")) {
								this.data.tabs[this.data.tab_current].container.addClass("current");
								break;
							}
						}
					}
				},
				set_mode_text: function () {
					var text = "Basic";
					if (this.data.mode == Settings.modes.ADVANCED_AND_BELOW) text = "Advanced";
					else if (this.data.mode == Settings.modes.TECHNICAL_AND_BELOW) text = "Technical";
					else if (this.data.mode == Settings.modes.DEBUG_AND_BELOW) text = "Debug";
					this.data.mode_changer.attr("value_display", text);
				},

				apply_change_status: function () {
					// Undisable button
					this.data.popup.container.find(".popup_button").first().removeClass("text_only");
				},
				save_changes: function () {
					// Get changes
					var changes = [];

					for (var i = 0, j, k, c; i < this.data.tabs.length; ++i) {
						for (j = 0; j < this.data.tabs[i].sections.length; ++j) {
							c = this.data.tabs[i].sections[j].contents;
							for (k = 0; k < c.length; ++k) {
								if (c[k].current_value != c[k].last_value) {
									// Add change
									changes.push(c[k]);
								}
							}
						}
					}

					// Set values
					var save_queue = [];
					for (var i = 0, j, t_data, t_array, ch_obj; i < changes.length; ++i) {
						// Set
						this_private.set_value.call(this, changes[i].target, changes[i].current_value, this.data.target_objects);

						// Append to save queue
						t_array = this_private.split_target.call(this, changes[i].target);
						t_data = this_private.get_target_data.call(this, t_array, this.data.target_objects);
						if (t_data != null) {
							// Change object
							ch_obj = {
								t_array: t_array.slice(t_data.offset),
								previous: changes[i].last_value
							};

							// Find what object/function to use
							for (j = 0; j < save_queue.length; ++j) {
								if (save_queue[j].object === t_data.target_data.object && save_queue[j].save_function === t_data.target_data.save_function) {
									save_queue[j].changes.push(ch_obj);
									break;
								}
							}
							if (j == save_queue.length) {
								save_queue.push({
									object: t_data.target_data.object,
									save_function: t_data.target_data.save_function,
									changes: [ ch_obj ]
								});
							}
						}

						// Clear change
						changes[i].last_value = changes[i].current_value;
						changes[i].container.removeClass("changed");
					}

					// Call save settings
					for (var i = 0, j, ret, fcns; i < save_queue.length; ++i) {
						ret = save_queue[i].save_function.call(save_queue[i].object, save_queue[i].changes);
						// Saving/applying functions
						if (ret instanceof Object) {
							if ("save" in ret) {
								fcns = this.match_functions(ret.save, save_queue[i].changes, true);
								for (j = 0; j < fcns.length; ++j) {
									fcns[j].call(save_queue[i].object, save_queue[i].changes);
								}
							}
							if ("apply" in ret) {
								fcns = this.match_functions(ret.apply, save_queue[i].changes, true);
								for (j = 0; j < fcns.length; ++j) {
									fcns[j].call(save_queue[i].object, save_queue[i].changes);
								}
							}
						}
					}

					// Disable save
					this.data.popup.container.find(".popup_button").first().addClass("text_only");
				},

				update_param_value: function (params, val) {
					// Change value
					if (params.current_value != val) {
						params.current_value = val;
						if (val == params.last_value) {
							// Same
							var cont = params.container, multiple, changes = false;
							if (!cont) {
								cont = params.parent_container;
								multiple = true;
							}
							else {
								multiple = cont.hasClass("multiple");
							}

							if (multiple) {
								// Check for changes; kind of a long loop since structures don't reference the parent
								var t = this.data.tabs, c, found = false;
								for (var i = 0, j, k; i < t.length; ++i) {
									for (j = 0; j < t[i].sections.length; ++j) {
										c = t[i].sections[j].contents;
										for (k = 0; k < c.length; ++k) {
											if (c[k].container == cont) {
												// Original container
												found = true;
												if (c[k].current_value != c[k].last_value) {
													changes = true;
													break;
												}
											}
											else if (found) {
												if (c[k].container == null) {
													// Child container
													if (c[k].current_value != c[k].last_value) {
														changes = true;
														break;
													}
												}
												else {
													// New container
													break;
												}
											}
										}
										if (found) break;
									}
									if (found) break;
								}
							}
							if (!changes) {
								cont.removeClass("changed");
							}
						}
						else {
							// Different
							this_private.apply_change_status.call(this);
							(params.container || params.parent_container).addClass("changed");
						}
					}
				},

				on_close: function (event, self) {
					self.data.popup = null;
					this_private.trigger.call(self, "close", {});

					// Close menus
					if (self.data.mode_menu != null) {
						self.data.mode_menu.close();
					}

					for (var i = 0, j, k, cont; i < self.data.tabs.length; ++i) {
						for (j = 0; j < self.data.tabs[i].sections.length; ++j) {
							cont = self.data.tabs[i].sections[j].contents;
							for (k = 0; k < cont.length; ++k) {
								if (cont[k].menu) {
									cont[k].menu.close();
								}
							}
						}
					}

					self.data.tabs = [];
					self.data.mode_changer = null;
					self.data.container = null;
				},
				on_button_click: function (event) {
					if (event.which != 1) return true;

					switch (event.data.callback_data.button_id) {
						case 0: // Close
						{
							event.data.popup.close();
						}
						break;
						case 1: // Save
						{
							if (!$(this).hasClass("text_only")) {
								this_private.save_changes.call(event.data.callback_data.self);
							}
						}
						break;
					}
				},

				on_tab_click: function (event) {
					if (event.which != 1) return true;

					var self = event.data.self.data;

					// Hide old tab
					if (self.tab_current >= 0) {
						self.tabs[self.tab_current].container.removeClass("current");
						self.tabs[self.tab_current].content.addClass("hidden");
					}

					// Show new
					self.tab_current = event.data.index;
					self.tabs[self.tab_current].container.addClass("current");
					self.tabs[self.tab_current].content.removeClass("hidden");

					// Scroll
					event.data.scroll_container.scrollTop(0);
				},

				on_mode_change_click: function (event) {
					if (event.which != 1) return true;

					var self = event.data.self.data;

					if (self.mode_menu == null) {
						// Options
						var options = [];
						var pairs = [
							["Basic",Settings.modes.NORMAL_AND_BELOW],
							["Advanced",Settings.modes.ADVANCED_AND_BELOW],
							["Technical",Settings.modes.TECHNICAL_AND_BELOW]
						];
						if (event.shiftKey) pairs.push(["Debug",Settings.modes.DEBUG_AND_BELOW]);

						for (var i = 0; i < pairs.length; ++i) {
							options.push({
								text: pairs[i][0],
								on: {
									click: {
										callback_data: {
											self: event.data.self,
											mode: pairs[i][1]
										},
										callback: this_private.on_mode_change_option_click
									}
								}
							});
						}

						// Menu
						self.mode_menu = new xch.Menu($(this), {
							close_on_mouseleave: false,
							keep_on_screen: true,
							open: "down",
							on: {
								close: {
									callback_data: event.data.self,
									callback: this_private.on_mode_change_menu_close
								}
							},
							options: options
						});
					}
				},
				on_mode_change_option_click: function (event) {
					if (event.which != 1) return true;

					event.data.option.menu.close();

					var m = event.data.callback_data.mode;
					if (m != event.data.callback_data.self.data.mode) {
						this_private.set_mode.call(event.data.callback_data.self, m);
					}
				},
				on_mode_change_menu_close: function (event, self) {
					self.data.mode_menu = null;
				},

				on_input_menu_click: function (event) {
					if (event.which != 1) return true;

					if (event.data.params.menu) {
						event.data.params.menu.close();
					}

					// Options
					var obj = $(this);
					var opts = event.data.params.value.options;
					var options = [], found = false;
					for (var i = 0; i < opts.length; ++i) {
						options.push({
							text: opts[i].label,
							on: {
								click: {
									callback_data: {
										value: opts[i].value,
										event_data: event.data,
										display: opts[i].label,
										obj: obj
									},
									callback: this_private.on_input_menu_option_click
								}
							}
						});
						if (!found && event.data.params.current_value == opts[i].value) {
							found = true;
						}
					}
					// Custom?
					if (!found) {
						options.push({
							text: this_private.param_value_to_string.call(event.data.self, event.data.params, true),
							on: {
								click: {
									callback_data: {
										value: event.data.params.current_value,
										event_data: event.data,
										obj: obj
									},
									callback: this_private.on_input_menu_option_click
								}
							}
						});
					}


					// Menu
					event.data.params.menu = new xch.Menu($(this), {
						close_on_mouseleave: false,
						keep_on_screen: true,
						open: "down",
						on: {
							close: {
								callback_data: event.data,
								callback: this_private.on_input_menu_close
							}
						},
						options: options
					});
					(event.data.params.container || event.data.params.parent_container).addClass("active");
				},
				on_input_menu_close: function (event, callback_data) {
					// Nullify menu reference
					callback_data.params.menu = null;
					(callback_data.params.container || callback_data.params.parent_container).removeClass("active");
				},
				on_input_menu_option_click: function (event) {
					if (event.which != 1) return true;

					// Close menu
					event.data.option.menu.close();

					// Get value
					var value = event.data.callback_data.value;

					// Change value
					var event_data = event.data.callback_data.event_data;
					this_private.update_param_value.call(event_data.self, event_data.params, value);

					// Change display
					event.data.callback_data.obj.attr("display_value", event.data.callback_data.display);
				},
				on_input_checkbox_change: function (event) {
					var obj = $(this);
					var val = obj.is(":checked");

					if (event.data.params.value.invert) val = !val;

					// Change value
					this_private.update_param_value.call(event.data.self, event.data.params, val);
				},
				on_input_radiobox_change: function (event) {
					var obj = $(this);
					var val = obj.is(":checked");

					if (!val) {
						// Re-select
						obj.prop("checked", true);
					}
					else {
						// De-select others
						for (var i = 0; i < event.data.inputs.length; ++i) {
							if (this == event.data.inputs[i].input[0]) {
								val = event.data.inputs[i].value;
							}
							else {
								event.data.inputs[i].input.prop("checked", false);
							}
						}

						// Value change
						this_private.update_param_value.call(event.data.self, event.data.params, val);
					}
				},
				on_input_textbox_focus: function (event) {
					var obj = $(this);
					var sel = xch.get_selection(obj);
					var new_val = this_private.param_value_to_string.call(event.data.self, event.data.params, false);

					// Different rep
					if (new_val != obj.val()) {
						obj.val(new_val);
						if (sel.start > new_val.length) sel.start = new_val.length;
						if (sel.end > new_val.length) sel.end = new_val.length;
						xch.set_selection(obj, sel.start, sel.end);
					}

					// Active
					(event.data.params.container || event.data.params.parent_container).addClass("active");
				},
				on_input_textbox_blur: function (event) {
					var obj = $(this);
					var val = obj.val();
					val = this_private.param_value_from_string.call(event.data.self, event.data.params, val);

					// Change value
					this_private.update_param_value.call(event.data.self, event.data.params, val);

					// Change value
					obj.val(
						this_private.param_value_to_string.call(event.data.self, event.data.params, true)
					);

					// Inactive
					(event.data.params.container || event.data.params.parent_container).removeClass("active");
				},
				on_input_slider_mousedown: function (event) {
					if (event.which != 1) return true;

					var par = event.data.grabber.parent();

					var data = {
						original: event.data,
						value: event.data.params.current_value,
						offset: 0,
						left: par.offset().left,
						width: par.width()
					};

					$(window)
					.on("mouseup", data, this_private.on_input_slider_window_mouseup)
					.on("mousemove", data, this_private.on_input_slider_window_mousemove);

					// Trigger
					this_private.on_input_slider_window_mousemove_update.call(event.data.self, data, event.pageX, event.shiftKey);

					// Active
					(event.data.params.container || event.data.params.parent_container).addClass("active");

					return false;
				},
				on_input_slider_grabber_mousedown: function (event) {
					if (event.which != 1) return true;

					var par = event.data.grabber.parent();

					var data = {
						original: event.data,
						value: event.data.params.current_value,
						offset: $(this).offset().left - event.pageX,
						left: par.offset().left,
						width: par.width()
					};

					$(window)
					.on("mouseup", data, this_private.on_input_slider_window_mouseup)
					.on("mousemove", data, this_private.on_input_slider_window_mousemove);

					// Active
					(event.data.params.container || event.data.params.parent_container).addClass("active");

					return false;
				},
				on_input_slider_window_mouseup: function (event) {
					// Turn off events
					$(window)
					.off("mouseup", this_private.on_input_slider_window_mouseup)
					.off("mousemove", this_private.on_input_slider_window_mousemove);

					// Update value
					this_private.update_param_value.call(event.data.original.self, event.data.original.params, event.data.value);

					// Inactive
					(event.data.original.params.container || event.data.original.params.parent_container).removeClass("active");
				},
				on_input_slider_window_mousemove: function (event) {
					// Change
					this_private.on_input_slider_window_mousemove_update.call(event.data.original.self, event.data, event.pageX, event.shiftKey);
				},

				on_input_slider_window_mousemove_update: function (mm_data, mouse_x, snap) {
					var value = Math.max(0, Math.min(1, (mouse_x - mm_data.left + mm_data.offset) / mm_data.width));

					// Adjust to range
					var val = mm_data.original.params.value;
					value = (value * (val.max - val.min)) + val.min;
					if (val.type == "integer") value = Math.round(value);
					if (mm_data.value == value) return; // no change

					// Snapping
					if ("snap" in val && (val.force_snap || ((!val.auto_snap) == snap))) {
						value = Math.round(value / val.snap) * val.snap;
					}

					// Position
					mm_data.original.grabber.css("left", ((value - val.min) / (val.max - val.min) * 100).toFixed(2) + "%");

					// Update display
					var d_max = ("decimals" in val ? val.decimals : 3);
					var d_min = ("decimals_min" in val ? val.decimals_min : 0);
					mm_data.original.value_display.attr("display_value", xch.format_decimal(value, d_max, d_min));

					if ("units" in val && (mm_data.value == 1) != (value == 1)) {
						mm_data.original.value_display.attr("display_units", value == 1 ? this_private.singular.call(this, val.units) : val.units);
					}

					// Set
					mm_data.value = value;
				},

				load_all: function () {
					for (var key in load) {
						load[key].call(this, false);
					}
				},

				on_cross_tab_save: function (event, self) {
					if (event.value in load) {
						load[event.value].call(self, true);
					}
				}

			};

			Settings.prototype = {

				constructor: Settings,

				add: function (params) {
					// Get the tab
					var tab = null;
					if ("tab" in params) {
						tab = (params.tab instanceof Tab ? params.tab : this.tab(params.tab));
					}
					else {
						tab = this.tabs[this.tabs.length - 1];
					}

					// Get the section
					var section = null;
					if ("section" in params) {
						section = (params.section instanceof Section ? params.section : tab.section(params.section));
					}
					else {
						return tab;
					}

					// Content addition
					if ("value" in params) {
						// Remove unneeded
						delete params["tab"];
						delete params["section"];
						if ("menu" in params) delete params["menu"];

						// Add defaults
						if (!("mode" in params)) {
							params.mode = Settings.modes.NORMAL;
						}
						params.container = null;

						// Values
						params.last_value = this_private.get_value.call(this, params.target, this.data.target_objects);
						params.current_value = params.last_value;

						// Append
						section.contents.push(params);
					}
					else {
						// Get the section
						return section;
					}
				},

				tab: function (tab_name) {
					for (var i = 0; i < this.data.tabs.length; ++i) {
						if (tab_name == this.data.tabs[i].name) return this.data.tabs[i];
					}
					var t = new Tab(tab_name);
					this.data.tabs.push(t);
					return t;
				},

				open: function (init_function) {
					this.data.popup = new xch.Popup({
						title: "Settings",
						html: this.create(init_function),
						size: {
							width: 640,
							height: "80%"
						},
						resize: true,
						scroll: false,
						on: {
							close: {
								callback: this_private.on_close,
								callback_data: this
							}
						},
						no_close: true,
						buttons: [{
							text: "Save",
							text_only: true,
							on: {
								click: {
									callback_data: {
										self: this,
										button_id: 1
									},
									callback: this_private.on_button_click
								}
							}
						}, {
							text: "Close",
							on: {
								click: {
									callback_data: {
										self: this,
										button_id: 0
									},
									callback: this_private.on_button_click
								}
							}
						}]
					});
				},
				is_open: function () {
					return (this.data.popup != null);
				},

				create: function (init_function) {
					// Init
					this.data.tabs = [];
					this.data.tab_current = 0;
					this.data.target_objects = this_private.get_target_objects.call(this);
					init_function.call(this);

					// Create
					var html, tab_container, content_container;
					(html = style.e("div", "settings"))
					.append( //{
						style.e("div", "settings_container")
						.append(
							style.e("div", "settings_tabs") // tabs
							.append(
								style.e("div", "settings_tabs_inner_1")
								.append(
									style.e("div", "settings_tabs_inner_2")
									.append(
										style.e("div", "settings_tabs_end_spacing")
									)
									.append(
										(tab_container = style.e("div", "settings_tabs_inner_3"))
									)
									.append(
										style.e("div", "settings_tabs_end_spacing")
										.append(
											style.e("div", "settings_tabs_end_spacing_inner")
											.append(
												(this.data.mode_changer = style.e("span", "settings_tabs_end_spacing_mode"))
											)
										)
									)
								)
							)
						)
						.append(
							style.e("div", "settings_sections") // content container
							.append(
								style.e("div", "settings_sections_inner_1")
								.append(
									style.e("div", "settings_sections_inner_2")
									.append(
										(content_container = style.e("div", "settings_sections_inner_3"))
									)
								)
							)
						)
					); //}

					// Events
					this_private.set_mode_text.call(this);
					this.data.mode_changer.on("click", { self: this }, this_private.on_mode_change_click);

					// Sections
					var tab_first = true, tab_count = 0, section_first, section_count, option_first, option_count, reselect_tab = false;
					for (var i = 0, j, k, c, s, s_outer, sec, item, data; i < this.data.tabs.length; ++i) {
						// Tab
						tab_container.append(
							(this.data.tabs[i].container = style.e("div", "settings_tab" + (i == this.data.tab_current ? " current" : "")))
							.append(
								style.e("div", "settings_tab_inner_1")
								.append(
									style.e("div", "settings_tab_inner_2")
									.text(this.data.tabs[i].name)
								)
							)
						);
						this.data.tabs[i].container.on("click", { self: this, index: i, tab: this.data.tabs[i], scroll_container: content_container }, this_private.on_tab_click);

						// Content
						content_container.append(
							(c = style.e("div", "settings_content" + (i == this.data.tab_current ? "" : " hidden")))
						);
						this.data.tabs[i].content = c;

						section_first = true;
						section_count = 0;
						for (j = 0; j < this.data.tabs[i].sections.length; ++j) {
							sec = this.data.tabs[i].sections[j];

							c.append(
								(s_outer = style.e("div", "settings_section"))
								.append(
									style.e("div", "settings_section_title")
									.text(sec.name)
								)
								.append(
									(s = style.e("div", "settings_section_content"))
								)
							);
							sec.container = s_outer;

							option_first = true;
							option_count = 0;
							data = {};
							for (k = 0; k < sec.contents.length; ++k) {
								// Append
								item = this_private.create_item.call(this, sec.contents[k], data);
								if (item != null) {
									s.append(item);
									// Item
									if ((sec.contents[k].mode & this.data.mode) != 0) {
										if (option_first) {
											item.addClass("first");
											option_first = false;
										}
										if ((++option_count % 2) == 1) {
											item.addClass("odd");
										}
									}
									else {
										item.addClass("hidden");
									}
								}
							}

							// Section
							if (option_count > 0) {
								if (section_first) {
									s_outer.addClass("first");
									section_first = false;
								}
								if ((++section_count % 2) == 1) {
									s_outer.addClass("odd");
								}
							}
							else {
								s_outer.addClass("hidden");
							}
						}

						// Tab
						if (section_count > 0) {
							if (tab_first) {
								this.data.tabs[i].container.addClass("first");
								tab_first = false;
							}
							if ((++tab_count % 2) == 1) {
								this.data.tabs[i].container.addClass("odd");
							}
						}
						else {
							this.data.tabs[i].container.addClass("hidden");
							if (this.data.tab_current == i) {
								reselect_tab = true;
							}
						}
					}

					// Tab change
					if (reselect_tab) {
						this.data.tabs[this.data.tab_current].container.removeClass("current");
						this.data.tabs[this.data.tab_current].content.addClass("hidden");
						for (this.data.tab_current = 0; this.data.tab_current < this.data.tabs.length; ++this.data.tab_current) {
							if (!this.data.tabs[this.data.tab_current].container.hasClass("hidden")) {
								this.data.tabs[this.data.tab_current].container.addClass("current");
								break;
							}
						}
					}

					// Done
					this.container = html;
					return html;
				},

				default_settings: function () {

					var tab, section;

					tab = this.add({ tab: "Posts" });
					section = this.add({ tab: tab, section: "Appearance" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "radio",
							reverse: true,
							on: "In footer",
							off: "In header"
						},
						target: "settings.post.footer_backlinks",
						label: "Post backlink location",
						description: "What location inside a post the backlinks should be placed",
						mode: Settings.modes.NORMAL
					});

					section = this.add({ tab: tab, section: "Date Formatting" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "string",
							select: "textbox"
						},
						target: "settings.post.time.format",
						label: "Time formatting",
						description: (
							style.e("div")
							.append(style.t("The time format to use; for reference, see "))
							.append(
								style.e("a")
								.attr("href", "http://php.net/manual/en/function.date.php")
								.attr("target", "_blank")
								.text("this link")
							)
						),
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: -86400,
							max: 86400,
							adjust_pre: function (value) {
								return value / 1000;
							},
							adjust_post: function (value) {
								return value * 1000;
							},
							units: "seconds"
						},
						target: "settings.post.time.offset",
						label: "Timezone offset",
						description: "Time to offset the server time by",
						mode: Settings.modes.NORMAL
					});

					section = this.add({ tab: tab, section: "Fetching" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Enabled",
							off: "Disabled"
						},
						target: "settings.post.fetching.new_priority",
						label: "New post priority",
						description: "Prioritize new posts when parsing a thread",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: 0,
							custom: {
								0: "Infinite"
							}
						},
						target: "settings.post.fetching.parsing_group.size",
						label: "Group size",
						description: "Number of posts to parse per group",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "number",
							select: "slider",
							min: 0,
							max: 1,
							decimals: 4,
							snap: 0.0625,
							units: "seconds"
						},
						target: "settings.post.fetching.parsing_group.interval",
						label: "Group timeout interval",
						description: "Timeout between parsing groups",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Single",
							off: "Multiple"
						},
						target: "settings.post.fetching.parsing_group.use_single",
						label: "Parsing queue count",
						description: "Use a single parsing queue for all threads",
						mode: Settings.modes.TECHNICAL
					});

					section = this.add({ tab: tab, section: "Inline Expansion" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Allow cycles",
							off: "No cycles"
						},
						target: "settings.post.inlining.allow_cyclic",
						label: "Allow cyclic embedding",
						description: "Allow the same post to be embedded multiple times in the same expansion chain",
						mode: Settings.modes.NORMAL
					});

					section = this.add({ tab: tab, section: "Inline Highlighting" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight",
							off: "Do nothing"
						},
						target: "settings.post.inlining.highlight_on_hover.if_floating.original",
						label: "Highlight original (floating)",
						description: "Highlight the original post when the target isn't embedded",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight",
							off: "Do nothing"
						},
						target: "settings.post.inlining.highlight_on_hover.if_embedded.original",
						label: "Highlight original (embedded)",
						description: "Highlight the original post when the target is embedded",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight",
							off: "Do nothing"
						},
						target: "settings.post.inlining.highlight_on_hover.if_floating.target",
						label: "Highlight cyclic (floating)",
						description: "Highlight the same post in the expansion when the target isn't embedded",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight",
							off: "Do nothing"
						},
						target: "settings.post.inlining.highlight_on_hover.if_embedded.target",
						label: "Highlight cyclic (embedded)",
						description: "Highlight the same post in the expansion when the target is embedded",
						mode: Settings.modes.ADVANCED
					});

					section = this.add({ tab: tab, section: "Inline Expansion Actions" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Display floating post",
							off: "No floating post"
						},
						target: "settings.post.inlining.on_screen.complete.inline",
						label: "When original post is completely on screen",
						description: [
							"Actions to take when the referenced post is completely on screen",
							"(Scrolling / highlighting only occur when embedding is disabled)"
						],
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Embed on click",
							off: "Don't embed"
						},
						target: "settings.post.inlining.on_screen.complete.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Scroll to original on click",
							off: "Don't scroll"
						},
						target: "settings.post.inlining.on_screen.complete.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight original on click",
							off: "Don't highlight"
						},
						target: "settings.post.inlining.on_screen.complete.highlight",
						mode: Settings.modes.TECHNICAL
					});

					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Display floating post",
							off: "No floating post"
						},
						target: "settings.post.inlining.on_screen.partial.inline",
						label: "When original post is partially on screen",
						description: [
							"Actions to take when the referenced post is partially on screen",
							"(Scrolling / highlighting only occur when embedding is disabled)"
						],
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Embed on click",
							off: "Don't embed"
						},
						target: "settings.post.inlining.on_screen.partial.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Scroll to original on click",
							off: "Don't scroll"
						},
						target: "settings.post.inlining.on_screen.partial.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight original on click",
							off: "Don't highlight"
						},
						target: "settings.post.inlining.on_screen.partial.highlight",
						mode: Settings.modes.TECHNICAL
					});

					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Display floating post",
							off: "No floating post"
						},
						target: "settings.post.inlining.on_screen.none.inline",
						label: "When original post is not on screen",
						description: [
							"Actions to take when the referenced post is completely off screen",
							"(Scrolling / highlighting only occur when embedding is disabled)"
						],
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Embed on click",
							off: "Don't embed"
						},
						target: "settings.post.inlining.on_screen.none.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Scroll to original on click",
							off: "Don't scroll"
						},
						target: "settings.post.inlining.on_screen.none.embed",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Highlight original on click",
							off: "Don't highlight"
						},
						target: "settings.post.inlining.on_screen.none.highlight",
						mode: Settings.modes.TECHNICAL
					});


					tab = this.add({ tab: "Sourcing" });
					section = this.add({ tab: tab, section: "Image Sourcing" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "string",
							select: "textarea",
							height: 240
						},
						target: "settings.sourcing.source_text",
						label: "Sourcing info for images",
						description: (
							style.e("div")
							.append(style.t("Available modifiers are:"))
							.append(style.e("br"))
							.append(style.t("%board - board letter"))
							.append(style.e("br"))
							.append(style.t("%thread_id - id number of the thread"))
							.append(style.e("br"))
							.append(style.t("%post_id - id number of the post"))
							.append(style.e("br"))
							.append(style.t("%thumbnail - url of the thumbnail image"))
							.append(style.e("br"))
							.append(style.t("%image - url of the full image"))
							.append(style.e("br"))
							.append(style.t("The % symbol can be escaped by placing 2 %'s in a row."))
							.append(style.e("br"))
							.append(style.t("A modifier can have a letter directly following it by using %modifier\\after_text."))
							.append(style.e("br"))
							.append(style.t("Empty lines and lines beginning with a # are ignored."))
							.append(style.e("br"))
							.append(style.t("Lines beginning with > symbols are treated as sub-menus."))
						),
						save: null,
						update: null,
						mode: Settings.modes.NORMAL
					});


					tab = this.add({ tab: "Updater" });
					section = this.add({ tab: tab, section: "Window Title" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "string",
							select: "textbox"
						},
						target: "updater.title.prefix",
						label: "Window title prefix",
						description: "Modifiers: %replies, %read, %unread",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "string",
							select: "textbox"
						},
						target: "updater.title.suffix",
						label: "Window title suffix",
						description: "Modifiers: %replies, %read, %unread",
						mode: Settings.modes.ADVANCED
					});

					section = this.add({ tab: tab, section: "Time Limits" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Auto start",
							off: "No auto start"
						},
						target: "updater.countdown_settings.auto_start",
						label: "Auto start",
						description: "Automatically start the countdown timer when opening a new thread",
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							units: "seconds"
						},
						target: "updater.countdown_settings.initial",
						label: "Initial countdown time",
						description: "Initial time the countdown timer waits when first started",
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							units: "seconds"
						},
						target: "updater.countdown_settings.minimum",
						label: "Minimum countdown time",
						description: "Minimum time the countdown timer waits before updating",
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							units: "seconds"
						},
						target: "updater.countdown_settings.maximum",
						label: "Maximum countdown time",
						description: "Maximum time the countdown timer waits before updating",
						mode: Settings.modes.NORMAL
					});

					section = this.add({ tab: tab, section: "Reset Behavior" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Never",
								value: xch.Updater.RESET_NEVER
							}, {
								label: "Only if time increases",
								value: xch.Updater.RESET_IF_INCREASES
							}, {
								label: "Only if time decreases",
								value: xch.Updater.RESET_IF_DECREASES
							}, {
								label: "Always",
								value: xch.Updater.RESET_ALWAYS
							}]
						},
						target: "updater.countdown_settings.reset_on_auto_enable",
						label: "Reset on auto update enabled",
						description: "Reset the time when automatic updating is changed to enabled",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Never",
								value: xch.Updater.RESET_NEVER
							}, {
								label: "Only if time increases",
								value: xch.Updater.RESET_IF_INCREASES
							}, {
								label: "Only if time decreases",
								value: xch.Updater.RESET_IF_DECREASES
							}, {
								label: "Always",
								value: xch.Updater.RESET_ALWAYS
							}]
						},
						target: "updater.countdown_settings.reset_on_manual_if_auto_enabled",
						label: "Reset on manual update (auto enabled)",
						description: "Reset the time when a manual update is performed while automatic updating is enabled",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Never",
								value: xch.Updater.RESET_NEVER
							}, {
								label: "Only if time increases",
								value: xch.Updater.RESET_IF_INCREASES
							}, {
								label: "Only if time decreases",
								value: xch.Updater.RESET_IF_DECREASES
							}, {
								label: "Always",
								value: xch.Updater.RESET_ALWAYS
							}]
						},
						target: "updater.countdown_settings.reset_on_manual_if_auto_disabled",
						label: "Reset on manual update (auto disabled)",
						description: "Reset the time when a manual update is performed while automatic updating is not enabled",
						mode: Settings.modes.TECHNICAL
					});

					section = this.add({ tab: tab, section: "Time Decrease Behavior" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: -1,
							custom: {
								"-1": "Minimum"
							},
							units: "seconds"
						},
						target: "updater.countdown_settings.decrease.amount",
						label: "Decrease amount",
						description: [
							"Decrease the countdown time by this amount when thread activity occurs",
							"-1 sets the value to the minimum"
						],
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: 1
						},
						target: "updater.countdown_settings.decrease.count_first",
						label: "Initial activity count",
						description: "Thread activity must occur this many times before the first time change",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: 1
						},
						target: "updater.countdown_settings.decrease.count",
						label: "Activity count",
						description: "Thread activity must occur this many times before subsequent time change",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Allowed",
							off: "Not allowed"
						},
						target: "updater.countdown_settings.decrease.manual",
						label: "Perform on manual update",
						description: "Allow a time change if a manual update was performed",
						mode: Settings.modes.TECHNICAL
					});

					section = this.add({ tab: tab, section: "Time Increase Behavior" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: -1,
							custom: {
								"-1": "Maximum"
							},
							units: "seconds"
						},
						target: "updater.countdown_settings.increase.amount",
						label: "Increase amount",
						description: [
							"Decrease the countdown time by this amount when thread activity occurs",
							"-1 sets the value to the maximum"
						],
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: 1
						},
						target: "updater.countdown_settings.increase.count_first",
						label: "Initial activity count",
						description: "Thread activity must occur this many times before the first time change",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "integer",
							select: "textbox",
							min: 1
						},
						target: "updater.countdown_settings.increase.count",
						label: "Activity count",
						description: "Thread activity must occur this many times before subsequent time change",
						mode: Settings.modes.TECHNICAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Allowed",
							off: "Not allowed"
						},
						target: "updater.countdown_settings.increase.manual",
						label: "Perform on manual update",
						description: "Allow a time change if a manual update was performed",
						mode: Settings.modes.TECHNICAL
					});


					tab = this.add({ tab: "Images" });

					var exts = [];
					for (var ext in this.image_preview.enabled) {
						if (ext == "spoiler") continue;
						exts.push({
							value: ext,
							label: xch.mime_types[xch.get_mime_type_from_extension(ext)].name + " (" + ext + ")"
						});
					}

					section = this.add({ tab: tab, section: "Preview Enabled" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Enabled",
							off: "Disabled"
						},
						target: "settings.image_preview.enabled.spoiler",
						label: "Enabled for spoiler images",
						description: "Enable or disable the preview popup for spoiler images",
						mode: Settings.modes.NORMAL
					});
					for (var i = 0; i < exts.length; ++i) {
						this.add({
							tab: tab,
							section: section,
							value: {
								type: "boolean",
								select: "checkbox",
								on: "Enabled",
								off: "Disabled"
							},
							target: "settings.image_preview.enabled[" + exts[i].value + "]",
							label: "Enabled for " + exts[i].value + " images",
							description: "Enable or disable the preview popup for " + exts[i].value + " images",
							mode: Settings.modes.ADVANCED
						});
					}
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: exts
						},
						target: "settings.image_preview.default",
						label: "Default image type",
						description: "The extension to use as a substitute for unknown extensions",
						mode: Settings.modes.ADVANCED
					});

					section = this.add({ tab: tab, section: "Preview Timeout" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "number",
							select: "slider",
							min: 0,
							max: 2,
							snap: 0.25,
							units: "seconds"
						},
						target: "settings.image_preview.open_timeout",
						label: "Open timeout",
						description: "Time to wait after mouse hover before the preview appears",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "number",
							select: "slider",
							min: 0,
							max: 2,
							snap: 0.25,
							units: "seconds"
						},
						target: "settings.image_preview.open_timeout_spoiler",
						label: "Open timeout (spoiler image)",
						description: "Time to wait after mouse hover before the preview appears for a spoiler image",
						mode: Settings.modes.ADVANCED
					});

					section = this.add({ tab: tab, section: "Preview Zooming" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Enabled",
							off: "Disabled"
						},
						target: "settings.image_preview.zooming.enabled",
						label: "Image zooming",
						description: "Enable zooming on image previews",
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Enabled",
							off: "Disabled"
						},
						target: "settings.image_preview.upscale",
						label: "Upscale images",
						description: "Scale up smaller images to fit the screen",
						mode: Settings.modes.NORMAL
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Invert",
							off: "Don't invert"
						},
						target: "settings.image_preview.zooming.invert",
						label: "Invert zoom location",
						description: "Invert the zoom origin based on where the cursor is",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "boolean",
							select: "checkbox",
							on: "Bounded",
							off: "Unbounded"
						},
						target: "settings.image_preview.zooming.bounded",
						label: "Cursor bounding",
						description: "Keep the cursor inside the preview, even when scaling down",
						mode: Settings.modes.ADVANCED
					});

					section = this.add({ tab: tab, section: "Preview Behavior" });
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Open preview",
								value: "toggle"
							}, {
								label: "Open in new tab",
								value: "tab"
							}, {
								label: "Embed image",
								value: "embed"
							}, {
								label: "Do nothing",
								value: "nothing"
							}]
						},
						target: "settings.image_preview.click.if_closed",
						label: "Image click action (when not previewing)",
						description: "What action to take when the image preview is not open and the image is clicked",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Close preview",
								value: "toggle"
							}, {
								label: "Open in new tab",
								value: "tab"
							}, {
								label: "Embed image",
								value: "embed"
							}, {
								label: "Do nothing",
								value: "nothing"
							}]
						},
						target: "settings.image_preview.click.if_open",
						label: "Image click action (when previewing)",
						description: "What action to take when the image preview is open and the image is clicked",
						mode: Settings.modes.ADVANCED
					});
					this.add({
						tab: tab,
						section: section,
						value: {
							type: "enum",
							select: "menu",
							options: [{
								label: "Close preview",
								value: "toggle"
							}, {
								label: "Open in new tab",
								value: "tab"
							}, {
								label: "Embed image",
								value: "embed"
							}, {
								label: "Do nothing",
								value: "nothing"
							}]
						},
						target: "settings.image_preview.click.if_preview",
						label: "Image preview click action",
						description: "What action to take when the image preview itself is clicked",
						mode: Settings.modes.ADVANCED
					});

				},

				on: function (event, callback, callback_data) {
					if (event in this.data.events) {
						this.data.events[event].callbacks.push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.data.events) {
						var e = this.data.events[event];
						if (e.triggering) {
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
						}
					}
				},

				match_functions: function (functions_descriptor, change_list, unique) {
					// Format data
					var fd = [], t_array, func, i, j, k, m;
					for (var key in functions_descriptor) {
						t_array = this_private.split_target.call(this, key);
						func = functions_descriptor[key];

						for (i = 0; i < fd.length; ++i) {
							if (fd[i].func == func) {
								fd[i].t_arrays.push(t_array);
								break;
							}
						}
						if (i == fd.length) {
							fd.push({
								t_arrays: [ t_array ],
								func: func
							});
						}
					}

					// Check for changes
					var fcns = [];
					for (i = 0; i < change_list.length; ++i) {
						for (j = 0; j < fd.length; ++j) {
							for (k = 0; k < fd[j].t_arrays.length; ++k) {
								if (fd[j].t_arrays[k].length <= change_list[i].t_array.length) {
									for (m = 0; m < fd[j].t_arrays[k].length; ++m) {
										if (fd[j].t_arrays[k][m] != change_list[i].t_array[m]) break;
									}
									if (m == fd[j].t_arrays[k].length) {
										// Make sure it's a unique function
										for (m = (unique ? 0 : fcns.length); m < fcns.length; ++m) {
											if (fcns[m] == fd[j].func) break;
										}
										if (m == fcns.length) {
											// Add
											fcns.push(fd[j].func);
										}
									}
								}
							}
						}
					}

					// Done
					return fcns;
				},
				get_change_apply_functions: function (change_list) {
					return {
						save: {
							"image_preview": save.image_preview,
							"sourcing": save.sourcing,
							"post": save.post,
							"popup": save.popup
						},
						apply: {
							"post.footer_backlinks": apply_changes.to_footer_backlinks
						}
					};
				}

			};

			Settings.modes = {
				NORMAL: 15,
				ADVANCED: 14,
				TECHNICAL: 12,
				DEBUG: 8,
				NORMAL_ONLY: 1,
				ADVANCED_ONLY: 2,
				TECHNICAL_ONLY: 4,
				DEBUG_ONLY: 8,
				NORMAL_AND_BELOW: 1,
				ADVANCED_AND_BELOW: 3,
				TECHNICAL_AND_BELOW: 7,
				DEBUG_AND_BELOW: 15
			};

			var Tab = function (name) {
				this.name = name;
				this.sections = [];
				this.container = null;
				this.content = null;
			};
			Tab.prototype = {

				constructor: Tab,

				section: function (section_name) {
					for (var i = 0; i < this.sections.length; ++i) {
						if (section_name == this.sections[i].name) return this.sections[i];
					}
					var s = new Section(section_name);
					this.sections.push(s);
					return s;
				}

			};

			var Section = function (name) {
				this.name = name;
				this.contents = [];
				this.container = null;
			};
			Section.prototype = {

				constructor: Section

			};

			return Settings;

		})(),


		// API
		API: (function () {

			var API = function () {
				this.prefix = "xch_api_";
				this.event_listen_function = null;

				this.events = {};

				if ("init" in this_private) {
					this_private.init.call(this);
				}
			};

			var this_private = {

				init: function () {
					// Remove
					delete this_private.init;

					// Getting
					this.gets = {};
					this.on_get = this_private.on_get;
					this.off_get = this_private.off_get;
					this.get_listen_function = null;

					// Acquire listening
					this.on("acquire", this_private.on_acquire, this);

					// API
					this.on_get("posts", this_private.on_get_posts);
				},

				on_get: function (event, callback, callback_data) {
					// callback format: function (event_data, callback_data, event_name) with (this instanceof xch.API)
					if (this.get_listen_function === null) {
						// API watching
						var self = this;
						document.addEventListener(this.prefix + "get", this.get_listen_function = (function (event) {
							this_private.receive_get.call(self, event.detail);
						}), false);
					}

					// Add event listener
					if (!(event in this.gets)) {
						this.gets[event] = {
							triggering: false,
							callbacks: [],
							removals: null
						};
					}

					// Add
					this.gets[event].callbacks.push([callback, callback_data]);
				},
				off_get: function (event, callback) {
					if (event in this.gets) {
						var e = this.gets[event];
						if (e.triggering) {
							// Queue for removal
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							// Remove any callbacks
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
							// Remove if empty
							if (e.length == 0) {
								delete this.gets[event];
							}
						}
					}
				},

				on_acquire: function (event, data, self) {
					return {
						API: xch.API,
						Message: xch.Message,
						Popup: xch.Popup,
						Menu: xch.Menu
					};
				},

				on_get_posts: function () {
					return (content ? content.on_get_posts() : []);
				},

				receive_get: function (data) {
					// Callbacks
					var ret = undefined;
					var get_name = data.value;
					if (get_name in this.gets) {
						// Trigger an event
						var e = this.gets[get_name];
						e.triggering = true;

						for (var i = 0, j = e.callbacks.length; i < j; ++i) {
							// Trigger
							ret = e.callbacks[i][0].call(this, get_name, e.callbacks[i][1]);
						}

						e.triggering = false;

						// Remove anything if necessary
						if (e.removals != null) {
							for (var i = 0; i < e.removals.length; ++i) {
								this.off_get(get_name, e.removals[i]);
							}
							e.removals = null;
						}
					}

					// Return
					data.return_value = ret || data.return_value;
				},

				receive: function (data) {
					var receiver_id = data.receiver_count || 0;

					// Callbacks
					var ret = undefined;
					var event_name = data.event;
					if (event_name in this.events) {

						// Trigger an event
						var ret_callback = (data.return_filter instanceof Function);
						var e = this.events[event_name];
						e.triggering = true;

						for (var i = 0, j = e.callbacks.length; i < j; ++i) {
							// Trigger
							ret = e.callbacks[i][0].call(this, data.data, e.callbacks[i][1], event_name);
							// Return callback
							if (ret_callback) {
								data.return_filter.call(this, ret, receiver_id, i);
							}
						}

						e.triggering = false;

						// Remove anything if necessary
						if (e.removals != null) {
							for (var i = 0; i < e.removals.length; ++i) {
								this.off(event_name, e.removals[i]);
							}
							e.removals = null;
						}

					}

					// Next receiver
					data.receiver_count = receiver_id + 1;

					// Return
					data.return_value = ret || data.return_value;
				}

			};

			API.prototype = {

				constructor: API,

				signal: function (event, data, return_filter) {
					// return_filter format: function (return_value, receiver_id, callback_id) with (this instanceof xch.API)
					var detail = {
						event: event,
						data: data,
						return_filter: return_filter,
						receiver_count: 0,
						return_value: undefined
					};
					document.dispatchEvent(new CustomEvent(this.prefix + "event", {
						detail: detail
					}));
					return detail.return_value;
				},
				on: function (event, callback, callback_data) {
					// callback format: function (event_data, callback_data, event_name) with (this instanceof xch.API)
					if (this.event_listen_function === null) {
						// API watching
						var self = this;
						document.addEventListener(this.prefix + "event", this.event_listen_function = (function (event) {
							this_private.receive.call(self, event.detail);
						}), false);
					}

					// Add event listener
					if (!(event in this.events)) {
						this.events[event] = {
							triggering: false,
							callbacks: [],
							removals: null
						};
					}

					// Add
					this.events[event].callbacks.push([callback, callback_data]);
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							// Queue for removal
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							// Remove any callbacks
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
							// Remove if empty
							if (e.length == 0) {
								delete this.events[event];
							}
						}
					}
				},

				get: function (value) {
					var detail = {
						value: value,
						return_value: undefined
					};
					document.dispatchEvent(new CustomEvent(this.prefix + "get", {
						detail: detail
					}));
					return detail.return_value;
				},

				e: function (tag) {
					var e = document.createElement(tag);
					if (arguments.length <= 1) {
						e.className = style.cls;
					}
					else {
						tag = style.cls;
						for (var i = 1; i < arguments.length; ++i) {
							tag += " ";
							tag += arguments[i];
						}
						e.className = tag;
					}
					return e;
				},
				checkbox: function (checked, size) {
					return style.checkbox(checked, size);
				},

				remove: function () {
					if (this.event_listen_function) {
						// Remove API listener
						document.removeEventListener(this.prefix + "event", this.event_listen_function, false);
						this.event_listen_function = null;
					}
					if (this.get_listen_function) {
						// Remove API listener
						document.removeEventListener(this.prefix + "get", this.get_listen_function, false);
						this.get_listen_function = null;
					}
				}

			};

			return API;

		})(),


		// Location
		Location: (function () {

			var Location = function () {
				this.hash_mark = "#";

				// Init
				this_private.update_state.call(this, true);

				// Events
				this.events = {
					"change": []
				};
				this_private.add_listeners.call(this);
			};

			var this_private = {

				add_listeners: function () {
					var self = this;

					window.addEventListener("popstate", this.on_window_popstate = (function () {
						this_private.update_state.call(self, false, false);
					}), false);
				},

				update_state: function (init, is_new) {
					// Fragment
					this.fragment = document.location.hash;
					if (this.fragment.length == 0) {
						this.fragment = null;
					}
					else if (this.fragment.substr(0, this.hash_mark.length) == this.hash_mark) {
						this.fragment = this.fragment.substr(this.hash_mark.length);
					}
					else if (this.fragment[0] == "#") {
						this.fragment = this.fragment.substr(1);
					}

					// Update
					if (!init) {
						this_private.trigger.call(this, "change", {
							location: this,
							is_new: is_new
						});
					}
				},
				trigger: function (event, data) {
					// Trigger an event
					var e = this.events[event];
					for (var i = 0; i < e.length; ++i) {
						e[i][0].call(this, data, e[i][1], event);
					}
				}

			};

			Location.prototype = {

				constructor: Location,

				go_to: function (href, overwrite_current) {
					if (overwrite_current) {
						history.replaceState({}, "go_to", href);
					}
					else {
						history.pushState({}, "go_to", href);
					}
					this_private.update_state.call(this, false, true);
				},
				go_to_new: function (href) {
					document.location = href;
				},
				form: function (params) {
					var url = "";
					// Path
					if ("path" in params) {
						url += params.path;
					}
					else {
						url += document.location.pathname;
					}
					// Search
					if ("search" in params) {
						if (params.search.length > 0) {
							if (params.search[0] != "?") url += "?";
							url += params.search;
						}
					}
					else {
						url += document.location.search;
					}
					// Fragment
					if ("fragment" in params) {
						if (params.fragment.length > 0) {
							if (params.fragment.substr(0, this.hash_mark.length) != this.hash_mark) url += this.hash_mark;
							url += params.fragment;
						}
					}
					// Done
					return url;
				},

				on: function (event, callback, callback_data) {
					if (event in this.events) {
						this.events[event].push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						for (var i = 0; i < e.length; ++i) {
							if (e[i][0] == callback) {
								e.splice(i, 1);
								--i;
							}
						}
					}
				},

				remove: function () {
					window.removeEventListener("popstate", this.on_window_popstate, false);
					this.on_window_popstate = null;
				}

			};

			return Location;

		})(),

		// Communication
		Communication: (function () {

			var Communication = function () {
				// Tab count management
				this.count_tabs = false;
				if (this.count_tabs) {
					this.ct_settings = {
						alive_update_rate: 30,
						alive_timeout: 90
					};

					this.ct_var_tab_count = xch.script.prefix + "_comm_ct_tab_count";
					this.ct_var_tab_time = xch.script.prefix + "_comm_ct_tab_time_";

					this.ct_alive_time_last = 0;
					this.ct_alive_update_interval = null;
					this.ct_init_time = 0;
				}

				this.sync_event_name = api.prefix + "storage_sync";

				this.tab_id = -1;
				this.site = null;
				this.events = {};

				// Opening
				this_private.on_open.call(this);

				this_private.add_listeners.call(this);
			};

			var this_private = {

				add_listeners: function () {
					var self = this;

					// Closing
					window.addEventListener("beforeunload", this.on_window_beforeunload = (function () {
						this_private.on_close.call(self);
					}), false);
				},

				on_open: function () {
					if (!this.count_tabs) return;

					var self = this;

					// Set init time
					var now = (new Date()).getTime();
					this.ct_init_time = now;

					// Get tab count
					var tab_count = xch.get_value(this.ct_var_tab_count, 0);
					var available_id = -1;
					if (tab_count > 0) {
						// Find available id
						var max_id = -1, timecode, i;
						for (i = 0; i < tab_count; ++i) {
							// Get the timecode
							timecode = xch.get_value(this.ct_var_tab_time + i, null);
							if (timecode === null) {
								// This id is usable
								if (available_id < 0) available_id = i;
							}
							else {
								if (now - timecode <= this.ct_settings.alive_timeout * 1000) {
									// Still alive
									max_id = i;
								}
								// Else, it should be dead (it has timed out)
							}
						}

						// Remove following data
						++max_id;
						for (i = max_id; i < tab_count; ++i) {
							// Delete old
							this_private.remove_tab_data.call(this, i, false);
						}

						// Update tab count
						tab_count = max_id;

						// Clear all?
						if (tab_count == 0) {
							this_private.clear_all_tab_counting_data.call(this);
						}
					}

					// Get id
					if (available_id < 0) available_id = tab_count;
					this.tab_id = available_id;
					if (this.tab_id == tab_count) ++tab_count;

					// Update values
					var my_time_id = this.ct_var_tab_time + this.tab_id;
					xch.set_value(this.ct_var_tab_count, tab_count);
					xch.set_value(my_time_id, now);
					this.ct_alive_time_last = now;
					now = null;

					// Update interval
					if (this.ct_alive_update_interval !== null) {
						clearInterval(this.ct_alive_update_interval);
					}
					this.ct_alive_update_interval = setInterval(
						function () {
							// Update time
							self.ct_alive_time_last = (new Date()).getTime();
							xch.set_value(my_time_id, self.ct_alive_time_last);
						},
						1000 * this.ct_settings.alive_update_rate
					);
				},
				on_close: function () {
					if (!this.count_tabs) return;

					// Stop alive interval
					if (this.ct_alive_update_interval !== null) {
						clearInterval(this.ct_alive_update_interval);
						this.ct_alive_update_interval = null;
					}

					// Remove data
					this_private.remove_tab_data.call(this, this.tab_id, true);
				},
				remove_tab_data: function (tab_id, is_this) {
					// Delete values
					xch.delete_value(this.ct_var_tab_time + tab_id);
				},
				clear_all_tab_counting_data: function () {
					// Nothing really
				},

				cross_tab_trigger: function (event, data) {
					if (event in this.events) {
						// Trigger an event
						var e = this.events[event];
						e.triggering = true;
						for (var i = 0, j = e.callbacks.length; i < j; ++i) {
							e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
						}
						e.triggering = false;
						if (e.removals != null) {
							for (var i = 0; i < e.removals.length; ++i) {
								this.cross_tab_off(event, e.removals[i]);
							}
							e.removals = null;
						}
					}
				},

				on_sync_event: function (data) {
					if (data.site == this.site) {
						this_private.cross_tab_trigger.call(this, data.event, data.data);
					}
				}

			};

			Communication.prototype = {

				constructor: Communication,

				ready: function (site) {
					var self = this;

					// Register site
					this.site = site;

					// Syncing
					window.addEventListener("storage", function (event) {
						if (event.key == self.sync_event_name && event.newValue !== null) {
							try {
								var val = JSON.parse(event.newValue);
								this_private.on_sync_event.call(self, val);
							}
							catch (e) {
							}
						}
					}, false);
				},

				cross_tab_trigger: function (event, data) {
					window.localStorage.setItem(this.sync_event_name, JSON.stringify({
						site: this.site,
						event: event,
						data: data
					}));
					window.localStorage.removeItem(this.sync_event_name);
				},
				cross_tab_on: function (event, callback, callback_data) {
					// Register event
					if (!(event in this.events)) this.events[event] = {
						triggering: false,
						callbacks: [],
						removals: null
					};

					// Add
					this.events[event].callbacks.push([callback, callback_data]);
				},
				cross_tab_off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
							// Remove if empty
							if (e.length == 0) {
								delete this.events[event];
							}
						}
					}
				},

				remove: function () {
					window.removeEventListener("beforeunload", this.on_window_beforeunload, false);
					this.on_window_beforeunload = null;
				}

			};

			return Communication;

		})(),


		// Header
		Header: (function () {

			var Header = function (html) {
				this.page_count = 0;
				this.page_index = 0;
				this.boards = [];

				this.board = "";
				this.board_title = "";
				this.board_subtitle = "";

				this.board_title_format = "/%board/ - %title";
				this.board_subtitle_format = "%subtitle";

				this.fixed = true;

				this.screen_area = {
					left: 0,
					top: 0,
					width: 0,
					height: 0
				};

				this.op_format = {
					max_length: 80,
					cutoff: "...",
					line_separator: " // "
				};

				this.page_format = {
					main: "page %current/%total",
					list: "page %current"
				};

				this.container = null;

				this.debug_popup = null;
				this.settings_popup = null;

				this.main_menu = null;

				this_private.parse.call(this, html);
			};

			var this_private = {

				parse: function (html) {
					// Board
					this.board = info.board;

					// Board title
					this.board_title = (html.find("body>header>h1:nth-of-type(1)").text() || "").trim().replace(/^\/\w+\/\s*-\s*/, "");

					// Board subtitle
					var p = html.find("body>header>div.subtitle");
					if (p.length > 0) {
						this.board_subtitle = (p.text() || "").trim();
					}

					// Remove
					html.find("body>header").remove();

					// Create board list
					var group = 0;
					var group_add = false;
					var bl = html.find("body>.boardlist:not(.bottom)");
					var bl_a = bl.children("a");
					for (var i = 0, m, href; i < bl_a.length; ++i) {
						href = $(bl_a[i]).attr("href");
						m = /\/\/boards\.38chan\.net\/(\w+)\/index\.html/i.exec(href);
						if (m) {
							if (m[1] == this.board) {
								this.add_board(m[1], group, this.board_title, this.board_subtitle);
							}
							else {
								this.add_board(m[1], group, null, null);
							}
							group_add = true;
						}
						if (group_add && bl_a[i].nextSibling) {
							if (!/\//.test(bl_a[i].nextSibling.nodeValue || "")) {
								++group;
								group_add = false;
							}
						}
					}

					// Remove board list
					bl.remove();
					html.find("body>.boardlist").remove();

					// Remove banner
					html.find("body>img.banner,body>div.banner").remove();

					// Remove title
					var title = html.find("body>.title"), pre;
					if ((pre = title.prev()).prop("tagName") == "H1") {
						pre.remove();
					}
					title.remove();

					// Remove line and script
					var hr = html.find("body>hr").first();
					hr.remove();

					// Pages
					var pages = html.find("body>.pages");
					if (pages.length > 0) {
						this.page_index = parseInt((pages.find(".selected").html() || "").trim()) || 0;
						this.page_count = parseInt((pages.find("a").last().html() || "").trim()) || 0;
						pages.remove();
					}

					// Remove footer stuff
					html.find("body>footer").remove();

					// Remove return link
					html.find("body>a").remove();

					// Stylize
					this_private.stylize.call(this, html);
				},

				stylize: function (html) {
					var self = this;

					var body = html.find("body");

					// Create the header
					var board_list, options, post_option, thread_option, menu_option;
					body.prepend( //{
						(this.container = style.e("div", "header" + (this.fixed ? " fixed" : "")))
						.append(
							style.e("div", "header_background")
						)
						.append(
							style.e("div", "header_content")
							.append(
								(board_list = style.e("div", "header_boards"))
							)
							.append(
								style.e("div", "header_custom")
								.append(
									(options = style.e("div", "header_options"))
									.append(
										(post_option = style.e("a", "header_option header_option_reply"))
										.text("post")
									)
									.append(
										(thread_option = style.e("a", "header_option header_option_thread"))
										.text("thread")
									)
									.append(
										(menu_option = style.e("a", "header_option header_option_menu"))
										.attr("target", "_blank")
										.attr("href", xch.script.homepage)
										.text(xch.script.name)
									)
								)
							)
						)
					) //}
					.prepend(
						style.e("div", "content_padding top")
					)
					.append(
						style.e("div", "content_padding bottom")
					);

					// Add boards
					var g_id = -1;
					var board_group, b, bn;
					for (var i = 0; i < this.boards.length; ++i) {
						if (g_id != this.boards[i].group) {
							board_group = board_list.children('.header_board_group[group_id="' + this.boards[i].group + '"]');
							if (board_group.length != 1) {
								board_list.append(
									(board_group = style.e("span", "header_board_group"))
									.attr("group_id", this.boards[i].group)
								);
							}
							g_id = this.boards[i].group;
						}

						// Add
						board_group.append(
							(b = style.e("span", "header_board"))
							.html(
								style.e("a", "header_board_link")
								.attr("href", "/" + this.boards[i].board + "/index.html")
								.append(
									style.e("span", "header_board_text_short")
									.html(this.boards[i].board)
								)
								.append(
									(bn = style.e("span", "header_board_text_name"))
								)
							)
						);
						if (this.boards[i].name) {
							bn.text(this.boards[i].name);
							b.addClass("named");
						}
						if (this.boards[i].subtitle) {
							bn.attr("title", this.boards[i].subtitle);
						}
						if (this.board == this.boards[i].board) {
							b.addClass("current");
						}
					}

					// Page selector
					if (this.page_count > 0) {
						var p_sel;
						options.prepend(
							(p_sel = style.e("a", "header_option header_option_page"))
							.text(this.format_page(this.page_format.main, this.page_index, this.page_count))
						);
						var p_sel_data = { self: this, menu: null };
						p_sel.on("mouseenter click", p_sel_data, this_private.on_page_selector_create_click);
					}

					// Events
					var data = { self: this };
					post_option.on("click", data, this_private.on_post_option_click);
					thread_option.on("click", data, this_private.on_thread_option_click);
					menu_option.on("click", data, this_private.on_menu_option_click);
					$(window).on("resize", data, this_private.on_window_resize);

					// Board info
					this.container.after(
						style.e("div", "board_info")
						.append( //{ Banner
							style.e("div", "board_banner_container")
							.append(
								style.e("img", "board_banner")
								.on("load", this_private.on_banner_load)
								.on("error", this_private.on_banner_error)
								.attr("src", "/banners/banner.php")
							)
						) //}
						.append(
							style.e("div", "board_info_title")
							.text(this.format_board_info(this.board_title_format))
						)
						.append(
							style.e("div", "board_info_subtitle")
							.text(this.format_board_info(this.board_subtitle_format))
						)
					);

					// Update
					this_private.update_screen_area.call(this);
				},

				on_page_selector_create_click: function (event) {
					// Create page selector
					this_private.create_page_selector.call(event.data.self, $(this), event.data);
				},
				on_post_option_click: function (event) {
					if (event.which != 1) return true;

					var qr = content.qr;
					if (!qr.is_open()) {
						qr.open();
						if (qr.settings.window.clear_post_on_reopen) {
							qr.clear();
						}
					}
					qr.focus_comment();

					return false;
				},
				on_thread_option_click: function (event) {
					if (event.which != 1) return true;

					return false;
				},
				on_menu_option_click: function (event) {
					if (event.which != 1) return true;

					this_private.create_main_menu.call(event.data.self, $(this));

					return false;
				},

				on_banner_load: function () {
					$(this).parent().addClass("loaded");
				},
				on_banner_error: function () {
					$(this).parent().addClass("error");
				},

				on_window_resize: function (event) {
					this_private.update_screen_area.call(event.data.self);
				},
				on_main_menu_close: function (event, self) {
					api.signal("main_menu_close", {
						menu: self.main_menu
					});
					self.main_menu = null;
				},
				on_main_menu_settings_click: function (event) {
					if (event.which != 1) return true;

					event.data.option.menu.close();

					event.data.callback_data.open_settings();

					return false;
				},
				on_main_menu_debug_info_click: function (event) {
					if (event.which != 1) return true;

					event.data.option.menu.close();

					this_private.popup_debug_info.call(event.data.callback_data);

					return false;
				},
				on_main_menu_homepage_click: function (event) {
					if (event.which != 1) return true;

					event.data.option.menu.close();
					//location.go_to_new(event.data.option.container.attr("href"));

					return true;//false;
				},

				create_page_selector: function (object, event_data) {
					if (event_data.menu == null) {
						var self = this;

						// Create page list
						var options = [];
						for (var i = 0; i < this.page_count; ++i) {
							options.push({
								html: (
									style.e("a", "page_selection")
									.attr("href", info.create_url.to_board(info.board, i + 1))
									.text(this.format_page(this.page_format.list, i + 1, this.page_count))
								),
								on: {
									click: this_private.on_page_menu_option_click
								}
							});
						}

						// Create menu
						event_data.menu = new xch.Menu(object, {
							close_on_mouseleave: true,
							fit_width_min: true,
							keep_on_screen: true,
							max_idle_time: 5.0,
							fixed_z: null,
							open: "down",
							on: {
								close: {
									callback_data: event_data,
									callback: this_private.on_page_menu_close
								}
							},
							options: options
						});
					}
				},
				create_main_menu: function (object) {
					if (this.main_menu != null) {
						this.main_menu.close();
					}
					var self = this;

					this.main_menu = new xch.Menu(object, {
						anchor: {
							x: {
								container: this.container,
								container_side: "right",
								menu_side: "right"
							}
						},
						menu_class: "expandable_point_left",
						menu_class_inherit: null,
						close_on_mouseleave: false,
						fit_width_min: true,
						keep_on_screen: true,
						fixed_z: null,
						open: "down",
						on: {
							close: {
								callback_data: this,
								callback: this_private.on_main_menu_close
							}
						},
						options: [{
							text: "Settings",
							on: {
								click: {
									callback_data: this,
									callback: this_private.on_main_menu_settings_click
								}
							}
						}, {
							text: "Debug Info",
							on: {
								click: {
									callback_data: this,
									callback: this_private.on_main_menu_debug_info_click
								}
							}
						}, {
							html: (
								style.e("a")
								.attr("target", "_blank")
								.attr("href", xch.script.homepage)
								.text("Homepage")
							),
							on: {
								click: {
									callback_data: this,
									callback: this_private.on_main_menu_homepage_click
								}
							}
						}]
					});

					api.signal("main_menu_open", {
						menu: this.main_menu
					});
				},

				popup_debug_info: function () {
					if (this.debug_popup != null && this.debug_popup.open) return;

					this.debug_popup = new xch.Popup({
						title: "Debug Information",
						text: "Any personal information (such as passwords, posted content, etc.) and unimportant information (such as the details of current threads/posts) has been omitted.",
						textarea: {
							height: 200,
							value: xch.get_debug_info(),
							readonly: true
						},
						buttons: [{
							text: "Close",
							on: {
								click: {
									callback_data: null,
									callback: function (event) {
										if (event.which != 1) return true;

										event.data.popup.close();
									}
								}
							}
						}],
						no_close: true,
						size: {
							width: 600
						}
					});
				},

				on_settings_close: function (event, self) {
					settings.off("close", this_private.on_settings_close);
				},

				on_page_menu_close: function (event, event_data) {
					event_data.menu = null;
				},
				on_page_menu_option_click: function (event) {
					if (event.which != 1) return true;

					event.data.option.menu.close();
					location.go_to_new(event.data.option.container.attr("href"));
					return false;
				},

				update_screen_area: function () {
					var h = this.container.outerHeight();
					if (!this.fixed) {
						h = Math.max(0, h - win.scrollTop());
					}

					var win = $(window);

					this.screen_area.left = 0;
					this.screen_area.top = h;
					this.screen_area.width = win.width();
					this.screen_area.height = win.height() - h;
				}

			};

			Header.prototype = {

				constructor: Header,

				add_board: function (board, group, name, subtitle) {
					this.boards.push({
						board: board,
						group: group,
						name: name,
						subtitle: subtitle
					});
				},

				get_height: function () {
					return (this.container.outerHeight());
				},
				is_fixed: function () {
					return (this.container.hasClass("fixed"));
				},
				get_screen_area: function () {
					return {
						left: this.screen_area.left,
						top: this.screen_area.top,
						width: this.screen_area.width,
						height: this.screen_area.height
					};
				},

				open_settings: function () {
					if (settings.is_open()) return;

					settings.on("close", this_private.on_settings_close, this);
					settings.open(settings.default_settings);
				},

				format_board_info: function (str) {
					var self = this;

					return xch.format_string(str, function (fmt) {
						if (fmt == "board") {
							fmt = self.board;
						}
						else if (fmt == "title") {
							fmt = self.board_title;
						}
						else if (fmt == "subtitle") {
							fmt = self.board_subtitle;
						}
						else if (fmt == "op") {
							fmt = self.format_op();
						}

						return fmt;
					});
				},
				format_op: function () {
					var op = "";

					// Find main thread
					var thread = null;
					for (var i = 0; i < content.threads.length; ++i) {
						if (content.threads[i].visible) {
							thread = content.threads[i];
							break;
						}
					}

					// Exists
					if (thread != null) {
						var thread_op = thread.posts[0];

						var val = thread_op.get_subject_plaintext();
						if (!val) {
							val = thread_op.get_comment_plaintext();
						}

						if (val) {
							val = val.trim();
							if (val.length > this.op_format.max_length + this.op_format.cutoff.length) {
								val = val.substr(0, this.op_format.max_length);
								val += this.op_format.cutoff;
							}
							op = val.replace(/(\r?\n|\r)+/g, this.op_format.line_separator);
						}
					}

					// Default
					if (op.length == 0) {
						op = this.board_title;
					}

					return op;
				},

				format_page: function (format, current, total) {
					return xch.format_string(format, function (fmt) {
						if (fmt == "current") fmt = current;
						else if (fmt == "total") fmt = total;
						return fmt;
					});
				}

			};

			return Header;

		})(),

		// Quick reply
		QuickReply: (function () {

			var QuickReply = function (html) {
				this.persistent = false;
				this.spoiler_available = false;
				this.target_thread = null;
				this.target_thread_is_current = false;

				this.target_selector = null;

				// Position and sizing settings
				this.popup = null;
				this.container = null;
				this.fields = null;

				this.position = {
					x: 64,
					y: 64,
					anchor: {
						x: 1,
						y: 0
					}
				};
				this.size = {
					width: 300,
					height: 120
				};

				// Post data settings
				this.post_data_names = {
					thread: "thread",
					board: "board",
					name: "name",
					email: "email",
					subject: "subject",
					comment: "body",
					file: "file",
					spoiler: "spoiler",
					password: "password"
				};
				this.post_data = [];
				this.post_data_id_current = 0;
				this.limits = {
					name: 50,
					email: 40,
					subject: 100,
					password: 18
				};
				this.post_context = {
					acquiring: false,
					percent: 0,
					thread: null,
					data_fields_extra: []
				};
				this.saved_post_settings = {
					password: window.localStorage.getItem("password") || "",
					name: window.localStorage.getItem("name") || "",
					email: window.localStorage.getItem("email") || ""
				};
				if (this.saved_post_settings.password.length == 0) {
					this.saved_post_settings.password = this.create_password(8);
					window.localStorage.setItem("password", this.saved_post_settings.password);
				}

				// Submitting settings
				this.submitting = {
					active: false,
					ajax: null,
					data: null,
					abortable: false,
					parsing: false,
					cleared_fields: false,
					queued: false,
					status: {
						progress: 0,
						step: null
					}
				};

				// Cooldown
				this.cooldown = {
					timer: null,
					time: 0,
					time_total: 1,
					retry_number: 0,
					complete: true,
					bar: null,
					bar_inner: null
				};

				// Settings
				this.settings = {
					window: {
						auto_open: {
							in_thread: false,
							on_index: false
						},
						clear_post_on_reopen: true
					},
					posting: {
						highlight_after_post: true,
						max_retries: 5,
						require_parse_complete_before_post_clear: true,
						require_parse_complete_before_posting_again: true,
						show_parse_status: true
					},
					cooldown: {
						parse_error: {
							timeout: 30,
							stop: false
						},
						http_error: {
							timeout: 10,
							stop: false
						},
						context_error: {
							timeout: 5,
							stop: false
						},
						connection_error: {
							timeout: 5,
							stop: false
						},
						posting_error: {
							timeout: 30,
							stop: false
						},
						post_retrieval_error: {
							timeout: 30,
							stop: true
						},
						success: {
							timeout: 30,
							stop: true
						}
					}
				};

				// Submission requirements
				this.requirements = {
					new_thread: {
						image: false,
						image_or_comment: true
					},
					reply: {
						image: false,
						image_or_comment: true
					}
				};
				this.valid_file_mimes = [
					"image/jpeg",
					"image/gif",
					"image/png",
					"image/bmp",
					"video/webm"
				];
				this.max_file_size = 10 * 1024 * 1024;
				this.post_submit_value = {
					new_thread: "New Topic",
					reply: "New Reply"
				};

				// Unsaveable settings
				this.unsaveable = {
					names: [],
					emails: ["sage","noko"]
				};

				// Messages
				this.info_message = null;
				this.error_message = null;

				// Load settings
				this_private.load_all.call(this);

				// Start
				this_private.init.call(this, html);
				this.set_target(this.target_thread);

				// Events
				content.on("thread_update", this_private.on_thread_updated, this);
				content.on("thread_load", this_private.on_thread_deleted, this);

				communication.cross_tab_on("qr_save", this_private.on_cross_tab_save, this);
				communication.cross_tab_on("qr_context_expire", this_private.on_cross_tab_context_change, this);
			};

			var this_private = {

				load_all: function () {
					// Load all settings
					this_private.load_position.call(this, false);
					this_private.load_size.call(this, false);
					this_private.load_persistency.call(this, false);
				},
				load_position: function (apply) {
					var pos = xch.site_get_value("qr_position", this.position);
					if (pos !== this.position) {
						xch.merge_settings(pos, this.position, false);

						if (apply) {
							if (this.popup) {
								this.popup.set_position(this.position.x, this.position.y, true, true);
							}
						}
					}
				},
				load_size: function (apply) {
					var size = xch.site_get_value("qr_size", this.size);
					if (size !== this.size) {
						xch.merge_settings(size, this.size, false);

						if (apply) {
							if (this.popup) {
								// Set size
								this.popup.set_size(this.size.width, this.size.height, true);
								this_private.update_size.call(this);
							}
						}
					}
				},
				load_persistency: function (apply) {
					this.persistent = xch.site_get_value("qr_persistent", this.persistent);

					if (apply) {
						this.set_persistency(this.persistent, true);
					}
				},
				save_position: function () {
					xch.site_set_value("qr_position", this.position);

					communication.cross_tab_trigger("qr_save", {
						value: "position"
					});
				},
				save_size: function () {
					xch.site_set_value("qr_size", this.size);

					communication.cross_tab_trigger("qr_save", {
						value: "size"
					});
				},
				save_persistency: function () {
					xch.site_set_value("qr_persistent", this.persistent);

					communication.cross_tab_trigger("qr_save", {
						value: "persistent"
					});
				},

				on_cross_tab_save: function (event, self) {
					if (event.value == "position") {
						this_private.load_position.call(self, true);
					}
					else if (event.value == "size") {
						this_private.load_size.call(self, true);
					}
					else if (event.value == "persistent") {
						this_private.load_persistency.call(self, true);
					}
					else if (event.value == "post_name") {
						this_private.load_post_name.call(self);
					}
					else if (event.value == "post_email") {
						this_private.load_post_email.call(self);
					}
				},
				on_cross_tab_context_change: function (event, self) {
					// Check if context needs an update
					var target = (self.target_thread_is_current ? self.target_thread : self.post_context.thread);
					var needs_update = false;
					if (target) {
						if (
							event.thread_id === target.id &&
							event.board == target.board
						) {
							needs_update = true;
						}
					}
					else if (event.thread_id === null) {
						needs_update = true;
					}

					// Update
					if (needs_update && !self.post_context.acquiring) {
						this_private.acquire_post_context.call(self, self.target_thread)
					}
				},

				on_thread_updated: function (event, self) {
					if (event.thread != self.target_thread) return;

					if (event.state_old.locked != self.target_thread.locked && self.target_thread.locked) {
						// Stop
						if (self.submitting.active) {
							this_private.submit_abort.call(self);
						}
						else if (self.submitting.queued) {
							this_private.submit_dequeue.call(self);
						}
					}
				},
				on_thread_deleted: function (event, self) {
					if (event.thread != self.target_thread) return;

					// Stop
					if (self.submitting.active) {
						this_private.submit_abort.call(self);
					}
					else if (self.submitting.queued) {
						this_private.submit_dequeue.call(self);
					}
				},

				init: function (html) {
					this_private.acquire_post_context_from_html.call(this, html, undefined, false);

					var post_form = html.find('form[name="post"]');
					if (post_form.length > 0) {
						// Spoiler
						this.spoiler_available = (post_form.find('input[name="spoiler"]').length > 0);

						// Remove
						post_form.remove();
					}

					// Open/create
					if (info.index ? this.settings.window.auto_open.on_index : this.settings.window.auto_open.in_thread) {
						this.open(null);
					}
				},

				cancel_event: function (event) {
					return (event.which != 1);
				},
				on_simple_change: function (event) {
					event.data.self.post_data[event.data.self.post_data_id_current][event.data.field].value = $(this).val();

					if ("save" in event.data) {
						event.data.save.compare.call(event.data.self);
					}
				},
				on_simple_blur: function (event) {
					var sel = xch.get_selection($(this));
					event.data.self.post_data[event.data.self.post_data_id_current][event.data.field].selection.start = sel.start;
					event.data.self.post_data[event.data.self.post_data_id_current][event.data.field].selection.end = sel.end;
				},
				on_simple_remove: function (event) {
					if (event.which != 1) return true;

					if ("save" in event.data) {
						var new_val = "";

						event.data.save.target.val(new_val);
						event.data.self.post_data[event.data.self.post_data_id_current][event.data.field].value = new_val;

						event.data.save.save.call(event.data.self, new_val);
					}
				},

				on_main_label_click: function (event) {
					if (event.which != 1) return true;
					// Change persistency
					event.data.self.set_persistency(!event.data.self.persistent, false);
				},
				on_main_label_target_click: function (event) {
					if (event.which != 1) return true;

					// Change thread
					if (event.data.self.target_selector_is_open()) {
						event.data.self.close_target_selector();
					}
					else {
						event.data.self.open_target_selector();
					}
					return false;
				},
				on_close_click: function (event) {
					if (event.which != 1) return true;

					// Close
					event.data.self.close();
				},
				on_grabber_mousedown: function (event) {
					if (event.which != 1) return true;

					event.data.self.moving.active = true;

					var o = event.data.self.container.offset();
					event.data.self.moving.offset.x = event.pageX - o.left;
					event.data.self.moving.offset.y = event.pageY - o.top;

					return false;
				},

				on_submit_click: function (event) {
					if (event.which != 1) return true;

					// Submit
					this_private.submit_try.call(event.data.self, event.shiftKey);
				},

				on_file_name_click: function (event) {
					if (event.which != 1) return true;

					if (!event.data.filesel.attr("readonly")) {
						if (event.data.self.post_data[event.data.self.post_data_id_current].file.file != null && (event.shiftKey || event.data.name_editing)) {
							var obj = $(this);
							obj.removeAttr("readonly");
							event.data.name_editing = true;
						}
						else {
							event.data.filesel.click();
						}
					}
				},
				on_file_name_blur: function (event) {
					if (event.data.name_editing) {
						// Updat ename
						var obj = $(this);
						obj.attr("readonly", "readonly");
						event.data.self.name_editing = false;

						var name = event.data.self.set_file_name(obj.val());
						if (name !== null) obj.val(name);
					}
				},
				on_file_remove_click: function (event) {
					if (event.which != 1) return true;

					if (!event.data.filesel.attr("readonly")) {
						event.data.self.set_file(null);
					}
				},
				on_file_change: function (event) {
					// Load file
					var files = event.target.files;
					if (files) {
						for (var i = 0; i < files.length; ++i) {
							if (event.data.self.set_file(files[i])) break;
						}
					}
					// Nullify
					this.value = null;
				},
				on_file_spoiler_change: function (event) {
					var obj = $(this);
					var ch = obj.is(":checked");
					if (obj.attr("readonly")) {
						ch = !ch;
						obj.prop("checked", ch);
					}
					event.data.self.post_data[event.data.self.post_data_id_current].spoiler.value = ch;
				},

				create: function () {
					// Objects
					var main_label_text, i_name, i_email, i_subject, i_comment, i_file_name, i_file, i_file_spoiler, i_file_spoiler_input, i_file_remove, submit;
					var i_name_remove, i_email_remove;

					// Create QR
					(this.container = style.e("div", "qr_content packed"))
					.append( //{ Top
						style.e("div", "qr_row top")
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "33%")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(i_name = style.e("input", "popup_input qr_input qr_name"))
									.attr("type", "text")
									.attr("placeholder", "Name")
									.attr("autocomplete", "off")
									.attr("maxlength", this.limits.name)
								)
								.append(
									style.e("div", "qr_saved_settings hidden")
									.append(style.e("div", "qr_saved_settings_v_aligner"))
									.append(
										style.e("span", "qr_saved_settings_inner")
										.append(
											(i_name_remove = style.e("a", "qr_saved_settings_unsave"))
											.attr("title", "Unsave name")
										)
									)
								)
							)
						)
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "34%")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(i_email = style.e("input", "popup_input qr_input qr_email"))
									.attr("type", "text")
									.attr("placeholder", "Email")
									.attr("autocomplete", "off")
									.attr("maxlength", this.limits.email)
								)
								.append(
									style.e("div", "qr_saved_settings hidden")
									.append(style.e("div", "qr_saved_settings_v_aligner"))
									.append(
										style.e("span", "qr_saved_settings_inner")
										.append(
											(i_email_remove = style.e("a", "qr_saved_settings_unsave"))
											.attr("title", "Unsave email")
										)
									)
								)
							)
						)
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "33%")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(i_subject = style.e("input", "popup_input qr_input qr_subject"))
									.attr("type", "text")
									.attr("placeholder", "Subject")
									.attr("autocomplete", "off")
									.attr("maxlength", this.limits.subject)
								)
							)
						)
					) //}
					.append( //{ Comment
						style.e("div", "qr_row")
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "100%")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(i_comment = style.e("textarea", "popup_input qr_input qr_comment"))
									.attr("placeholder", "Comment")
									.attr("autocomplete", "off")
									.css("height", this.size.height + "px")
								)
							)
						)
					) //}
					.append( //{ Bottom
						style.e("div", "qr_row bottom")
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "100%")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(i_file_name = style.e("input", "popup_input qr_input qr_file_name"))
									.attr("type", "text")
									.attr("placeholder", "File")
									.attr("readonly", "readonly")
									.attr("autocomplete", "off")
								)
								.append(
									style.e("div", "qr_file_settings hidden")
									.append(style.e("div", "qr_file_settings_v_aligner"))
									.append(
										style.e("span", "qr_file_settings_inner")
										.append(
											(i_file_spoiler = style.checkbox(false, "small"))
											.addClass("qr_file_spoiler qr_file_settings_item")
											.attr("title", "Spoiler image")
										)
										.append(
											(i_file_remove = style.e("a", "qr_file_remove qr_file_settings_item"))
											.attr("title", "Remove file")
										)
										.append(
											(i_file = style.e("input", "qr_file qr_file_settings_item"))
											.attr("type", "file")
											.attr("multiple", "multiple")
										)
									)
								)
							)
						)
						.append(
							style.e("div", "qr_cell")
							.attr("target_width", "64px")
							.append(
								style.e("div", "qr_cell_inner")
								.append(
									(submit = style.e("button", "popup_button qr_button qr_submit"))
									.append(
										style.e("span", "qr_submit_text")
									)
								)
							)
						)
					) //}
					.append( //{ Status
						style.e("div", "qr_status hidden")
						.append(
							style.e("div", "qr_status_inner")
							.append(
								style.e("div", "qr_status_text")
								.append(
									style.e("span", "qr_status_message")
								)
								.append(
									style.e("span", "qr_status_attempt hidden")
								)
							)
							.append(
								style.e("div", "qr_progress default")
								.append(
									style.e("div", "qr_progress_inner")
								)
							)
						)
					); //}

					// Properly style cells
					var qr_cells = this.container.find(".qr_cell_inner");
					for (var i = 0; i < qr_cells.length; ++i) {
						$(qr_cells[i].firstChild)
						.after(style.e("div", "qr_cell_inner_t"))
						.after(style.e("div", "qr_cell_inner_l"))
						.after(style.e("div", "qr_cell_inner_b"));
					}

					// Cooldown container
					this.container.find(".qr_submit").next()
					.addClass("qr_cooldown_container")
					.append(
						(this.cooldown.bar = style.e("div", "qr_cooldown ready"))
						.append(
							(this.cooldown.bar_inner = style.e("div", "qr_cooldown_inner"))
						)
					);

					// Spoiler
					if (!this.spoiler_available) {
						i_file_spoiler.addClass("hidden");
					}

					// Popup
					this.popup = new xch.Popup({
						title: (
							(this.main_label = style.e("div", "qr_main_label"))
							.append(
								(main_label_text = style.e("span", "qr_main_label_prefix"))
							)
							.append(
								style.e("span", "qr_main_label_target")
								.append(
									(this.main_label_target_link = style.e("a", "qr_main_label_target_text"))
									.attr("target", "_blank")
								)
							)
						),
						html: this.container,
						size: {
							width: 320,
							// height: 240
						},
						packed: true,
						resize: {
							h_object: i_comment
						},
						size_min: {
							width: 64,
							height: 20
						},
						scroll: false,
						on: {
							before_close: {
								callback_data: this,
								callback: this_private.on_popup_before_close
							},
							resize: {
								callback_data: this,
								callback: this_private.on_popup_resize
							},
							resizing: {
								callback_data: this,
								callback: this_private.on_popup_resizing
							},
							move: {
								callback_data: this,
								callback: this_private.on_popup_move
							}
						}
					});

					// Persistent
					if (this.persistent) this.main_label.addClass("persistent");

					// Events
					var data = { self: this };

					this.main_label.on("mousedown", this_private.cancel_event);

					main_label_text.on("mousedown", this_private.cancel_event)
					.on("click", data, this_private.on_main_label_click);

					this.main_label_target_link.on("mousedown", this_private.cancel_event)
					.on("click", data, this_private.on_main_label_target_click);

					submit.on("click", data, this_private.on_submit_click);

					// Input events
					var i_data;
					i_name.on("change", i_data = { self: this, field: "name", save: { compare:this_private.compare_post_name, save:this_private.save_post_name, target:i_name } }, this_private.on_simple_change);
					i_name.on("blur", i_data, this_private.on_simple_blur);
					i_name_remove.on("click", i_data, this_private.on_simple_remove);

					i_email.on("change", i_data = { self: this, field: "email", save: { compare:this_private.compare_post_email, save:this_private.save_post_email, target:i_email } }, this_private.on_simple_change);
					i_email.on("blur", i_data, this_private.on_simple_blur);
					i_email_remove.on("click", i_data, this_private.on_simple_remove);

					i_subject.on("change", i_data = { self: this, field: "subject" }, this_private.on_simple_change);
					i_subject.on("blur", i_data, this_private.on_simple_blur);

					i_comment.on("change", i_data = { self: this, field: "comment" }, this_private.on_simple_change);
					i_comment.on("blur", i_data, this_private.on_simple_blur);

					i_file_name
					.on("click", i_data = { self: this, filesel: i_file, name_editing: false }, this_private.on_file_name_click)
					.on("blur", i_data, this_private.on_file_name_blur);
					i_file_remove.on("click", i_data, this_private.on_file_remove_click);
					i_file.on("change", data, this_private.on_file_change);
					(i_file_spoiler_input = i_file_spoiler.find("input")).on("change", data, this_private.on_file_spoiler_change);

					// List of all fields
					this.fields = i_name.add(i_email).add(i_subject).add(i_comment).add(i_file).add(i_file_spoiler_input);

					// Size
					this.popup.set_size(this.size.width, this.size.height, true);
					this_private.update_size.call(this);
				},

				on_popup_before_close: function (event, self) {
					self.close();
					return false;
				},
				on_popup_resize: function (event, self) {
					// Size
					this_private.update_size.call(self);

					// Save
					self.position.x = self.popup.position.x;
					self.position.y = self.popup.position.y;
					self.size.width = self.popup.size.width;
					self.size.height = self.popup.size.height;
					this_private.save_position.call(self);
					this_private.save_size.call(self);
				},
				on_popup_resizing: function (event, self) {
					// Update values
					self.position.x = self.popup.position.x;
					self.position.y = self.popup.position.y;
					self.size.width = self.popup.size.width;
					self.size.height = self.popup.size.height;
					// Update size
					this_private.update_size.call(self);
				},
				on_popup_move: function (event, self) {
					self.position.x = self.popup.position.x;
					self.position.y = self.popup.position.y;

					// Save
					this_private.save_position.call(self);
				},

				resize: function (mouse) {
					// Normalize
					var win = $(window);
					mouse.x -= win.scrollLeft();
					mouse.y -= win.scrollTop();
					var pos = this.container.offset();
					var conts = [ this.container.children(".popup_content") , this.container.find(".qr_comment") ];
					var diff, pos_update = false;
					var xy = ["x","y"];
					var wh = ["width","height"];

					// Resize
					for (var i = 0; i < 2; ++i) {
						if (this.resizing[xy[i]] != 0 && this.resizing[xy[i]] != this.position.anchor[xy[i]]) {
							// Difference
							diff = (mouse[xy[i]] - this.resizing.base_mouse[xy[i]]) * this.resizing[xy[i]];
							// Bound
							if (this.resizing.base_size[wh[i]] + diff < this.size_min[wh[i]]) {
								diff = this.size_min[wh[i]] - this.resizing.base_size[wh[i]];
							}

							// Set values
							this.size[wh[i]] = this.resizing.base_size[wh[i]] + diff;
							if (this.resizing[xy[i]] == -1) {
								this.position[xy[i]] = this.resizing.base_pos[xy[i]] - diff;
								pos_update = true;
							}

							// Apply
							conts[i].css(wh[i], this.size[wh[i]] + "px");
						}
					}

					// Update
					if (pos_update) {
						this.set_position(this.position.x, this.position.y, true, true);
					}
					this_private.update_size.call(this);
				},

				update_size: function () {
					// Sizing
					var full_size = this.container.innerWidth();
					if (full_size < 1) full_size = 1;
					var rows = this.container.find(".qr_row");
					for (var r = 0, i; r < rows.length; ++r) {
						// Init
						var total_percent = 0;
						var total_pixels = 0;
						var pixels = [];
						var percent = [];

						// Group
						var input_containers = $(rows[r]).children();
						for (i = 0; i < input_containers.length; ++i) {
							var ic = $(input_containers[i]);
							var tw = ic.attr("target_width");
							var m, val;
							if ((m = /([0-9]+)px/.exec(tw))) {
								val = parseInt(m[1]);
								pixels.push([ic, val]);
								total_pixels += val;
							}
							else if ((m = /([0-9]+)%/.exec(tw))) {
								val = parseInt(m[1]) / 100;
								percent.push([ic, val]);
								total_percent += val;
							}
							else if (tw == "auto") {
								input_containers[i].style.width = "";
								val = ic.outerWidth();
								pixels.push([ic, val]);
								total_pixels += val;
							}
						}

						// Size pixels
						var scale = 1;
						if (total_percent == 0 || total_pixels > full_size) {
							scale = full_size / total_pixels;
							total_pixels = full_size;
						}
						for (i = 0; i < pixels.length; ++i) {
							pixels[i][0].css("width", (pixels[i][1] * scale) + "px");
						}

						// Size percents
						scale = (total_percent == 0 ? 0 : 1.0 / total_percent) * (full_size - total_pixels);
						for (i = 0; i < percent.length; ++i) {
							percent[i][0].css("width", (percent[i][1] * scale) + "px");
						}

						// Height
						var max_height = 0;
						input_containers.css("height", "");
						for (i = 0; i < input_containers.length; ++i) {
							max_height = Math.max(max_height, $(input_containers[i]).outerHeight());
						}
						input_containers.css("height", max_height + "px");
					};
				},

				create_post_data: function () {
					// Empty data
					var data = {
						name: {
							value: "",
							selection: {
								start: 0,
								end: 0
							}
						},
						email: {
							value: "",
							selection: {
								start: 0,
								end: 0
							}
						},
						subject: {
							value: "",
							selection: {
								start: 0,
								end: 0
							}
						},
						comment: {
							value: "",
							selection: {
								start: 0,
								end: 0
							}
						},
						file: {
							name: "",
							file: null
						},
						spoiler: {
							value: false
						}
					};

					// Saved settings
					data.name.value = this.saved_post_settings.name;
					data.email.value = this.saved_post_settings.email;

					// Done
					return data;
				},

				acquire_post_context: function (thread) {
					this.post_context.acquiring = true;
					this.post_context.percent = 0;

					// Query URL
					xch.ajax({
						url: (thread == null ? info.create_url.to_board(info.board) : info.create_url.to_thread(thread.board, thread.id)),
						method: "GET",
						return_type: "text",
						on: {
							load: this_private.on_post_context_load,
							error: this_private.on_post_context_error,
							abort: this_private.on_post_context_abort,
							progress: this_private.on_post_context_progress
						},
						qr: this,
						qr_pc_thread: thread
					});
				},
				acquire_post_context_from_html: function (html, thread, expire_old) {
					// Find data
					var post_form = html.find('form[name="post"]');
					if (post_form.length > 0) {
						// Reset
						this.post_context.data_fields_extra = [];

						// Extra fields
						var ignore_fields = ["thread", "board", "post"];
						for (var field in this.post_data_names) {
							ignore_fields.push(this.post_data_names[field]);
						}

						var inputs = post_form.find("input,textarea");
						for (var j = 0; j < inputs.length; ++j) {
							var me = $(inputs[j]);
							var name = me.attr("name");
							for (var i = 0; i < ignore_fields.length; ++i) {
								if (name == ignore_fields[i]) break;
							}
							if (i < ignore_fields.length) continue; // Skip
							// Add
							this.post_context.data_fields_extra.push({
								name: name,
								value: me.val()
							});
						}

						this.post_context.thread = thread;

						// Context expired
						if (expire_old) {
							communication.cross_tab_trigger("qr_context_expire", {
								board: (thread ? thread.board : null),
								thread_id: (thread ? thread.id : null)
							});
						}
					}
				},
				on_post_context_load: function (event) {
					// HTML Parse
					if (event.okay) {
						// Parse response
						var html = xch.string_to_dom(event.response);
						// Check
						if (html != null && (html = $(html)).length > 0) {
							// Parse
							this_private.acquire_post_context_from_html.call(event.data.qr, html, event.data.qr_pc_thread, true);
						}
					}

					// Done
					this_private.post_context_acquire_finish.call(event.data.qr);
				},
				on_post_context_error: function (event) {
					this_private.post_context_acquire_finish.call(event.data.qr);
				},
				on_post_context_abort: function (event) {
					this_private.post_context_acquire_finish.call(event.data.qr);
				},
				on_post_context_progress: function (event) {
					var perc = event.event.loaded / event.event.total;

					event.data.qr.post_context.percent = perc;

					this_private.submit_progress_change.call(event.data.qr, "context_acquire", perc);
				},
				post_context_acquire_finish: function () {
					// Done
					this.post_context.acquiring = false;

					// Percent change
					this.post_context.percent = 0;
					this_private.submit_progress_change.call(this, "context_acquire", 1);

					// Next
					if (this.submitting.queued && this.cooldown.complete) {
						this_private.submit.call(this);
					}
				},

				on_info_message_close: function (event, self) {
					self.info_message = null;
				},
				on_error_message_close: function (event, self) {
					self.error_message = null;
				},

				submit_try: function (skip_timeout) {
					if (this.cooldown.complete) {
						if (this.submitting.active) {
							// Can abort?
							if (this.submitting.abortable) {
								// Abort
								this_private.submit_abort.call(this);
							}
						}
						else if (this.post_context.acquiring) {
							if (this.submitting.queued) {
								// De-queue
								this_private.submit_dequeue.call(this);
							}
							else {
								// Queue
								this.cooldown.retry_number = 0;
								this_private.update_retry_number_display.call(this);
								this_private.submit_queue.call(this);
							}
						}
						else {
							// Submit
							var submit_now = true;
							if (!this.target_thread_is_current) {
								// If target mismatch, don't submit
								if (this.target_thread == null) {
									if (this.post_context.thread) {
										submit_now = false;
									}
								}
								else {
									if (this.target_thread != this.post_context.thread) {
										submit_now = false;
									}
								}
							}

							if (submit_now) {
								// Submit should be okay
								this.cooldown.retry_number = 0;
								this_private.update_retry_number_display.call(this);
								this_private.submit.call(this);
							}
							else {
								// Context re-acquire
								this_private.acquire_post_context.call(this, this.target_thread);
								// Queue
								this.cooldown.retry_number = 0;
								this_private.update_retry_number_display.call(this);
								this_private.submit_queue.call(this);
							}
						}
					}
					else if (!this.submitting.active) {
						if (skip_timeout) {
							if (this.post_context.acquiring) {
								if (this.submitting.queued) {
									// De-queue
									this_private.submit_dequeue.call(this);
								}
								else {
									// Queue
									this.cooldown.retry_number = 0;
									this_private.update_retry_number_display.call(this);
									this_private.submit_queue.call(this);
								}
							}
							else {
								// Submit
								this.cooldown.retry_number = 0;
								this_private.update_retry_number_display.call(this);
								this_private.cooldown_stop.call(this);
								this_private.submit.call(this);
							}
						}
						else {
							// Queue
							if (this.submitting.queued) {
								// De-queue
								this_private.submit_dequeue.call(this);
							}
							else {
								// Queue
								this.cooldown.retry_number = 0;
								this_private.update_retry_number_display.call(this);
								this_private.submit_queue.call(this);
							}
						}
					}
				},

				submit_abort: function () {
					if (this.submitting.active && this.submitting.ajax && this.submitting.abortable) {
						this.submitting.ajax.abort();
					}
				},
				submit_queue: function () {
					if (!this.submitting.queued) {
						// Validate
						var submit_data = this.post_data[0];
						var err = this.validage_post(submit_data);
						if (err != null) {
							this_private.submit_error.call(this, {
								type: "Invalid Post",
								detail: err.detail,
								cooldown: null
							});
							return;
						}

						// Queue
						this.submitting.queued = true;

						if (!this.submitting.active) {
							this_private.change_fields_readonly.call(this, true);

							this_private.submit_status_show.call(this);
							if (this.cooldown.complete) {
								this_private.submit_status_change.call(this, "Acquiring post context...", "context_acquire", this.post_context.percent);
							}
							else {
								this_private.submit_status_change.call(this, "Cooldown timer...", "cooldown", 1 - (this.cooldown.time / this.cooldown.time_total));
							}
						}
					}
				},
				submit_dequeue: function () {
					if (this.submitting.queued) {
						this.submitting.queued = false;

						if (!this.submitting.active) {
							this_private.submit_status_hide.call(this);
							this_private.change_fields_readonly.call(this, false);
						}

						this.container.find(".qr_submit").removeClass("retry");
					}
				},

				submit: function () {
					// Make sure data is not being submitted
					if (this.submitting.active) return

					// Get submit data
					var submit_data = this.post_data[0];

					// Validate
					var err = this.validage_post(submit_data);
					if (err != null) {
						this_private.submit_error.call(this, {
							type: "Invalid Post",
							detail: err.detail,
							cooldown: null
						});
						return;
					}

					// Readonly
					this_private.change_fields_readonly.call(this, true);

					// Status change
					this_private.submit_status_show.call(this);
					this_private.submit_status_change.call(this, "Connecting...", "upload", 0);

					this.container.find(".qr_submit").addClass("active").removeClass("retry");

					this_private.cooldown_stop.call(this);

					this.submitting.active = true;
					this.submitting.data = ajax_data;
					this.submitting.abortable = true;
					this.submitting.parsing = false;
					this.submitting.cleared_fields = false;
					this.submitting.queued = false;

					// Create form data
					var fd = this_private.create_submit_form_data.call(this, submit_data);

					// Submit target
					var target_post_url = "";
					if (this.target_thread != null) {
						target_post_url = info.create_url.to_post_submit_target(this.board, this.thread);
					}
					else {
						target_post_url = info.create_url.to_post_submit_target(info.board, null);
					}

					// Begin
					var ajax_data = {
						url: target_post_url,
						method: "POST",
						return_type: "text",
						post_data: fd,
						//cred: true,
						on: {
							load: this_private.submit_on_load,
							progress: this_private.submit_on_progress,
							error: this_private.submit_on_error,
							abort: this_private.submit_on_abort,
							upload: {
								load: this_private.submit_on_upload_load,
								abort: this_private.submit_on_upload_abort,
								error: this_private.submit_on_upload_error,
								progress: this_private.submit_on_upload_progress
							}
						},
						qr: this,
						qr_target_thread: this.target_thread,
						qr_submit_data: submit_data,
						qr_password: this.saved_post_settings.password
					};
					this.submitting.ajax = xch.ajax(ajax_data);
				},

				submit_progress_change: function (step, progress) {
					if (this.submitting.status.step != step) return;

					// Set
					this.submitting.status.progress = progress;

					// Change width
					this.container.find(".qr_progress_inner").css("width", (this.submitting.status.progress * 100).toFixed(2) + "%");
				},
				submit_status_change: function (text, step, progress) {
					// Set
					var step_pre = this.submitting.status.step;
					this.submitting.status.step = step;
					this.submitting.status.progress = progress;

					// Text
					var status = this.container.find(".qr_status");
					if (arguments.length > 0) status.find(".qr_status_message").text(text);

					// Progress
					this_private.submit_progress_change.call(this, step, this.submitting.status.progress);

					// Progress colors
					if (step_pre != step) {
						var pb = status.find(".qr_progress");
						var pbi = pb.children(".qr_progress_inner");
						style.set_class(pb, "qr_progress " + step_pre);
						pbi.removeClass(step_pre).addClass(step);
					}
				},
				submit_status_show: function () {
					var status = this.container.find(".qr_status");
					if (status.hasClass("hidden")) {
						this.submitting.status.step = "default";

						this.container.find(".qr_status").removeClass("hidden");
					}
				},
				submit_status_hide: function () {
					var pb = (this.container.find(".qr_status").addClass("hidden").find(".qr_progress"));
					var pbi = pb.children(".qr_progress_inner");
					style.set_class(pb, "qr_progress default");
					style.set_class(pbi, "qr_progress_inner");
				},

				submit_on_load: function (event) {
					// Status
					this_private.submit_status_change.call(event.data.qr, "Download complete", "download", 1);

					// Change
					if (event.okay) {
						// Parse response
						var html = xch.string_to_dom(event.response);
						// Check
						if (html != null && (html = $(html)).length > 0) {
							// Parse
							this_private.submit_check.call(event.data.qr, html, event.data, event);
						}
						else {
							// Bad
							this_private.submit_error.call(event.data.qr, {
								type: "Parsing Error",
								detail: "An error occured while parsing the resulting HTML",
								cooldown: "parse_error"
							});
						}
					}
					else {
						this_private.submit_error.call(event.data.qr, {
							type: "Status Error",
							detail: "An invalid http status was returned (" + event.status + ", " + event.status_text + ")",
							cooldown: "http_error"
						});
					}
				},
				submit_on_abort: function (event) {
					// Error message
					if (event.data.qr.info_message != null) {
						event.data.qr.info_message.close();
					}
					event.data.qr.info_message = new xch.Message({
						type: "info",
						title: "Submission Cancelled",
						text: "Post download was cancelled",
						on: {
							close: {
								callback_data: event.data.qr,
								callback: this_private.on_info_message_close
							}
						}
					});

					// Done
					this_private.submit_complete.call(event.data.qr, event.data.qr_submit_data, true);
				},
				submit_on_error: function (event) {
					this_private.submit_error.call(event.data.qr, {
						type: "Error",
						detail: "An error occured while connecting",
						cooldown: "connection_error"
					});
				},
				submit_on_progress: function (event) {
					var perc = event.event.loaded / event.event.total;
					this_private.submit_progress_change.call(event.data.qr, "download", perc);
				},
				submit_on_upload_load: function (event) {
					this_private.submit_status_change.call(event.data.qr, "Downloading...", "download", 0);
				},
				submit_on_upload_abort: function (event) {
					// Error message
					if (event.data.qr.info_message != null) {
						event.data.qr.info_message.close();
					}
					event.data.qr.info_message = new xch.Message({
						type: "info",
						title: "Submission Cancelled",
						text: "Post upload was cancelled",
						on: {
							close: {
								callback_data: event.data.qr,
								callback: this_private.on_info_message_close
							}
						}
					});

					// Done
					this_private.submit_complete.call(event.data.qr, event.data.qr_submit_data, true);
				},
				submit_on_upload_error: function (event) {
					this_private.submit_error.call(event.data.qr, {
						type: "Error",
						detail: "An error occured while uploading",
						cooldown: "connection_error"
					});
				},
				submit_on_upload_progress: function (event) {
					var perc = event.event.loaded / event.event.total;
					if (event.data.qr.submitting.status.progress == 0 && perc > 0) {
						this_private.submit_status_change.call(event.data.qr, "Uploading...", "upload", perc);
					}
					else {
						this_private.submit_progress_change.call(event.data.qr, "upload", perc);
					}
				},

				submit_posting_error_check: function (ajax_data, error_str) {
					if (
						// /you\s+look\s+like\s+a\s+bot/i.test(error_str)
						/looks\s+automated/i.test(error_str)
					) {
						// Acquire new posting context
						this_private.acquire_post_context.call(this, ajax_data.qr_target_thread);
						return true;
					}
					return false;
				},
				submit_error: function (error) {
					// Error message
					if (this.error_message != null) {
						this.error_message.close();
					}
					this.error_message = new xch.Message({
						type: "error",
						title: error.type,
						text: error.detail,
						on: {
							close: {
								callback_data: this,
								callback: this_private.on_error_message_close
							}
						}
					});

					// Clear status
					this.submitting.active = false;
					this.submitting.data = null;
					this.submitting.abortable = false;
					this.submitting.parsing = false;
					this.submitting.ajax = null;
					this.submitting.cleared_fields = false;
					this.container.find(".qr_submit").removeClass("active");

					// Cooldown
					this_private.cooldown_start.call(this, "cooldown" in error ? error.cooldown : null, true);

					// Unlock fields
					if (!this.submitting.queued) {
						this_private.change_fields_readonly.call(this, false);
					}
				},
				submit_check: function (html, ajax_data, event) {
					this.submitting.abortable = false;

					// Error checking
					var error = content.check_html_for_error(html);
					if (error !== null) {
						var cooldown_type = this_private.submit_posting_error_check.call(this, ajax_data, error) ? "context_error" : "posting_error";

						// Remove last "."
						if (error[error.length - 1] == "." && error.substr(-2) != "..") {
							error = error.substr(0, error.length - 1);
						}

						this_private.submit_error.call(this, {
							type: "Posting Error",
							detail: error,
							cooldown: cooldown_type
						});
						return false;
					}

					// Otherwise, good
					this_private.submit_okay.call(this, html, ajax_data, event);

					// Done
					return true;
				},
				submit_okay: function (html, ajax_data, event) {
					// Loader settings
					var loader, loader_board, loader_thread;
					if (ajax_data.qr_target_thread == null) {
						// New thread
						loader_board = info.board;
						loader_thread = undefined;
					}
					else {
						// Reply
						loader_board = ajax_data.qr_target_thread.board;
						loader_thread = ajax_data.qr_target_thread.id;
					}
					// Loader
					loader = xch.Loader.get_thread_from_ajax_load(
						loader_board,
						loader_thread,
						null,
						{},
						ajax_data,
						event,
						html
					);

					// Status change
					this.submitting.parsing = true;
					if (this.settings.posting.show_parse_status) {
						// Change status
						this_private.submit_status_change.call(event.data.qr, "Parsing response...", "parse", 0);
					}
					else if (!this.submitting.queued) {
						this_private.submit_status_hide.call(this);
					}

					loader.on("progress", this_private.submit_on_parse_progress, this);

					// How to use the loader
					if (ajax_data.qr_target_thread == null) {
						// New thread
						this_private.setup_submitted_thread_find.call(this, ajax_data, loader, true);
						loader.run();
					}
					else {
						// Comment
						if (info.index || content.updater == null) {
							// Find comment and go to thread
							this_private.setup_submitted_post_find.call(this, ajax_data, loader, true);
							loader.run();
						}
						else {
							// Use thread updater
							this_private.setup_submitted_post_find.call(this, ajax_data, loader, false);
							content.updater.update_from_loader(loader, null);
						}
					}

					// Start the cooldown timer
					this_private.cooldown_start.call(this, "success", false);

					// Clear post
					if (!this.settings.posting.require_parse_complete_before_post_clear && !this.submitting.cleared_fields) {
						this.submitting.cleared_fields = true;
						this.submitting.queued = false;
						this_private.change_fields_readonly.call(this, false);
						this.clear();
					}

					// Post context
					this_private.acquire_post_context_from_html.call(this, html, ajax_data.qr_target_thread, true);
				},
				submit_complete: function (submit_data, aborted) {
					// Submission complete
					this.submitting.active = false;
					this.submitting.data = null;
					this.submitting.abortable = false;
					this.submitting.parsing = false;
					this.submitting.ajax = null;

					// Save
					this_private.save_post_name.call(this, submit_data.name.value);
					this_private.save_post_email.call(this, submit_data.email.value);

					// Clear post
					if (!this.submitting.cleared_fields) {
						this.submitting.queued = false;
						this_private.change_fields_readonly.call(this, false);
						if (!aborted) this.clear();
					}
					this.submitting.cleared_fields = false;

					// Clear status
					this.container.find(".qr_submit").removeClass("active");

					// Enable cooldown to finish
					this.cooldown.retry_number = 0;
					this_private.update_retry_number_display.call(this);
					this_private.cooldown_finish.call(this, false);

					// Hide status
					if (!this.submitting.queued) {
						this_private.submit_status_hide.call(this);
						// QR close?
						if (!aborted && !this.persistent) {
							this.close();
						}
					}
				},

				submit_on_parse_progress: function (event, self) {
					if (event.type == "parse") {
						this_private.submit_progress_change.call(self, "parse", event.loaded / event.total);
					}
				},

				update_retry_number_display: function () {
					var status = this.container.find(".qr_status");

					if (this.cooldown.retry_number == 0) {
						status.find(".qr_status_attempt").addClass("hidden").text("");
					}
					else {
						status.find(".qr_status_attempt").removeClass("hidden").text("attempt " + (this.cooldown.retry_number + 1));
					}
				},

				cooldown_start: function (cooldown_type, source_error) {
					/*!debug!*/assert(cooldown_type == null || cooldown_type in this.settings.cooldown, "Bad cooldown setting: " + cooldown_type);
					// Apply
					var type = (cooldown_type == null ? null : this.settings.cooldown[cooldown_type]);
					if (cooldown_type == null || !source_error || type.stop || ++this.cooldown.retry_number >= this.settings.posting.max_retries) {
						// No auto-retry
						this.submitting.queued = false;

						// Reset retry number
						if (source_error) {
							this.cooldown.retry_number = 0;
							this_private.submit_status_hide.call(this);
						}
					}
					else {
						// Auto-retry
						this.submitting.queued = true;

						// Change status
						this.container.find(".qr_submit").addClass("retry");
						this_private.submit_status_change.call(this, "Cooldown timer...", "cooldown", 1 - (this.cooldown.time / this.cooldown.time_total));
					}

					this_private.update_retry_number_display.call(this);

					// Start cooldown timer
					if (cooldown_type != null) {
						var self = this;

						this.cooldown.bar.removeClass("ready");

						var time = type.timeout;
						if (this.cooldown.timer !== null) {
							clearInterval(this.cooldown.timer);
							// Decrease from total time if already active
							if (source_error) {
								time -= (this.cooldown.time_total - this.cooldown.time);
							}
						}

						this.cooldown.complete = false;
						this.cooldown.time_total = type.timeout;
						this.cooldown.time = Math.min(time, type.timeout);

						this_private.cooldown_tick.call(this, false);
						this.cooldown.timer = setInterval(function () {
							this_private.cooldown_tick.call(self, true);
						}, 1000);
					}
				},
				cooldown_tick: function (decrease) {
					var finish = false;
					if (decrease && --this.cooldown.time <= 0) {
						// Stop timer
						if (this.cooldown.timer !== null) {
							clearInterval(this.cooldown.timer);
							this.cooldown.timer = null;
						}

						finish = true;
					}

					// Update bar percent
					var perc = 1 - (this.cooldown.time / this.cooldown.time_total);
					this.cooldown.bar_inner.css("width", (perc * 100).toFixed(2) + "%");
					this_private.submit_progress_change.call(this, "cooldown", perc);

					// Finish
					if (finish) {
						this_private.cooldown_finish.call(this, true);
					}
				},
				cooldown_stop: function () {
					// Stop timer
					if (this.cooldown.timer !== null) {
						clearInterval(this.cooldown.timer);
						this.cooldown.timer = null;
					}

					// Complete
					if (!this.cooldown.complete) {
						this.cooldown.complete = true;
						this.cooldown.bar.addClass("ready");
					}
				},
				cooldown_finish: function (end_of_timer) {
					if (this.cooldown.complete) return;

					var finish = false;
					if (end_of_timer) {
						if (!this.settings.posting.require_parse_complete_before_posting_again || !this.submitting.active || this.submitting.queued) {
							finish = true;
						}
					}
					else {
						if (this.cooldown.timer === null) {
							finish = true;
						}
					}

					if (finish) {
						// Done
						this.cooldown.complete = true;
						this.cooldown.bar.addClass("ready");
						this.container.find(".qr_submit").removeClass("retry");

						// Retry
						if (this.submitting.queued) {
							if (this.post_context.acquiring) {
								this_private.submit_status_change.call(this, "Acquiring post context...", "context_acquire", this.post_context.percent);
							}
							else {
								// (re)Submit
								this_private.submit.call(this);
							}
						}
					}
				},

				compare_post_name: function () {
					if (this.container != null) {
						var field = this.fields.filter(".qr_name");
						var field_ss = field.parent().children(".qr_saved_settings");

						if (this.saved_post_settings.name != null && this.saved_post_settings.name.length > 0 && this.post_data[this.post_data_id_current].name.value == this.saved_post_settings.name) {
							field_ss.removeClass("hidden");
							field.css("padding-right", (field_ss.outerWidth()) + "px");
						}
						else {
							field_ss.addClass("hidden");
							field.css("padding-right", "");
						}
					}
				},
				compare_post_email: function () {
					if (this.container != null) {
						var field = this.fields.filter(".qr_email");
						var field_ss = field.parent().children(".qr_saved_settings");

						if (this.saved_post_settings.email != null && this.saved_post_settings.email.length > 0 && this.post_data[this.post_data_id_current].email.value == this.saved_post_settings.email) {
							field_ss.removeClass("hidden");
							field.css("padding-right", (field_ss.outerWidth()) + "px");
						}
						else {
							field_ss.addClass("hidden");
							field.css("padding-right", "");
						}
					}
				},
				load_post_name: function () {
					this.saved_post_settings.name = window.localStorage.getItem("name") || "";
					this_private.compare_post_name.call(this);
				},
				load_post_email: function () {
					this.saved_post_settings.email = window.localStorage.getItem("email") || "";
					this_private.compare_post_email.call(this);
				},
				save_post_name: function (value) {
					for (var i = 0; i < this.unsaveable.names.length; ++i) {
						if (value == this.unsaveable.names[i]) {
							value = null;
							break;
						}
					}

					if (value !== null) {
						window.localStorage.setItem("name", value);
						this.saved_post_settings.name = value;
					}

					this_private.compare_post_name.call(this);

					// Multitab signal
					communication.cross_tab_trigger("qr_save", {
						value: "post_name"
					});
				},
				save_post_email: function (value) {
					for (var i = 0; i < this.unsaveable.emails.length; ++i) {
						if (value == this.unsaveable.emails[i]) {
							value = null;
							break;
						}
					}

					if (value !== null) {
						window.localStorage.setItem("email", value);
						this.saved_post_settings.email = value;
					}

					this_private.compare_post_email.call(this);

					// Multitab signal
					communication.cross_tab_trigger("qr_save", {
						value: "post_email"
					});
				},

				which_post_is_more_accurate: function (submit_data, posts) {
					// Returns -1 if posts[0] and posts[1] both match submit_data equally
					// Returns 0 if posts[0] more closely matches submit_data
					// Returns 1 if posts[1] more closely matches submit_data
					var p = [0,0], name, s_val, p_val, pos;

					for (var i = 0; i < p.length; ++i) {
						// Name
						name = submit_data.name.value.trim();
						pos = name.indexOf("#");
						s_val = (pos < 0 ? name : name.substr(0, pos));
						p_val = posts[i].get_name_plaintext();
						if (p_val == s_val || (s_val.length == 0 && info.is_default_name(p_val, posts[i].thread.board))) {
							++p[i];
						}

						// Tripcode
						s_val = (pos < 0 ? null : name.substr(pos + 1));
						p_val = posts[i].get_tripcode_plaintext();
						if ((s_val === null) == (p_val === null)) {
							++p[i];
						}

						// Subject
						s_val = submit_data.subject.value.trim();
						p_val = posts[i].get_subject_plaintext();
						if (
							(s_val.length == 0 && p_val === null) ||
							(s_val.length > 0 && p_val !== null && s_val == p_val.trim())
						) {
							++p[i];
						}

						// Image
						if (
							(submit_data.file.file != null) === (posts[i].image != null && posts[i].image.good())
						) {
							++p[i]
						}
					}

					// Comment matching
					if (Math.abs(p[0] - p[1]) <= 1) {
						var word_matches = [0,0];
						for (var i = 0; i < p.length; ++i) {
							// Comment
							s_val = submit_data.comment.value.trim();
							p_val = posts[i].get_comment_plaintext() || "";
							word_matches[i] = this_private.get_word_matches.call(this, s_val, p_val);
						}
						if (word_matches[0] > word_matches[1]) ++p[0];
						else if (word_matches[1] > word_matches[0]) ++p[1];
						if (p[0] == p[1]) {
							// Adjust again; this should give priority to word-based matching
							if (word_matches[0] > word_matches[1]) ++p[0];
							else if (word_matches[1] > word_matches[0]) ++p[1];
						}
					}

					// Return
					if (p[0] == p[1]) return -1;
					else if (p[0] > p[1]) return 0;
					return 1;
				},
				get_word_matches: function (base, compare) {
					// Returns an approximate rating of how closely 2 strings match; 0 = not at all, 1 = very close (if not identical) match
					var b = base.split(/\W+/);
					var c = compare.split(/\W+/);
					var b_length = b.length;
					var matches = 0;

					for (var i = 0, j; i < c.length; ++i) {
						for (j = 0; j < b.length; ++j) {
							if (c[i] == b[j]) {
								b.splice(j, 1);
								++matches;
								break;
							}
						}
					}

					return Math.max(0, matches - Math.abs(b_length - c.length)) / b_length;
				},

				setup_submitted_thread_find: function (ajax_data, loader, redirect_to_thread) {
					var cb_data = {
						self: this,
						new_post: null,
						new_thread: null,
						finish_function: this_private.on_submitted_thread_loader_finish,
						redirect_to_thread: redirect_to_thread,
						submit_data: ajax_data.qr_submit_data,
						password: ajax_data.qr_password
					};

					// Events
					loader.on("thread_ready", this_private.on_submitted_thread_loader_thread_ready, cb_data);
					loader.on("post_load", this_private.on_submitted_thread_loader_post_load, cb_data);
					loader.on("complete", this_private.on_submitted_thread_loader_complete, cb_data);
				},
				on_submitted_thread_loader_finish: function (cb_data) {
					// Mark post as "you"
					content.mark_post_as_me(cb_data.new_post, cb_data.submit_data, cb_data.password, true);
					this_private.submit_complete.call(cb_data.self, cb_data.submit_data, false);

					new xch.Message({
						type: "okay",
						title: "Post Successful",
						text: "Your thread was successfully submitted"
					});

					// Go to thread
					if (cb_data.redirect_to_thread) {
						// Go to thread
						location.go_to_new(
							info.create_url.to_thread(cb_data.new_post.thread.board, cb_data.new_post.thread.id)
						);
					}
				},
				on_submitted_thread_loader_thread_ready: function (event, cb_data) {
					// Main thread
					if (cb_data.new_thread == null) {
						cb_data.new_thread = event.thread;
					}
				},
				on_submitted_thread_loader_post_load: function (event, cb_data) {
					// Main post
					if (cb_data.new_post == null && event.post.op && event.post.thread == cb_data.new_thread) {
						// Finish
						cb_data.new_post = event.post;
						cb_data.finish_function.call(cb_data.self, cb_data);
						cb_data.finish_function = null;
					}
				},
				on_submitted_thread_loader_complete: function (event, cb_data) {
					// Check
					if (cb_data.new_thread == null) {
						// Error
						this_private.submit_error.call(self, {
							type: "Post Retrieval Error",
							detail: "Your thread could not be found after submission",
							cooldown: "post_retrieval_error"
						});
					}
					else if (cb_data.finish_function != null) {
						if (cb_data.new_post == null) {
							for (var i = 0; i < cb_data.new_thread.posts.length; ++i) {
								if (cb_data.new_thread.posts[i].op) {
									cb_data.new_post = cb_data.new_thread.posts[i];
									break;
								}
							}
						}
						if (cb_data.new_post == null) {
							// Error
							this_private.submit_error.call(cb_data.self, {
								type: "Post Retrieval Error",
								detail: "Your thread's OP could not be found after submission",
								cooldown: "post_retrieval_error"
							});
						}
						else {
							// Finish
							cb_data.finish_function.call(cb_data.self, cb_data);
							cb_data.finish_function = null;
						}
					}
				},

				setup_submitted_post_find: function (ajax_data, loader, redirect_to_thread) {
					var cb_data = {
						self: this,
						new_posts: [],
						redirect_to_thread: redirect_to_thread,
						target_thread: ajax_data.qr_target_thread,
						submit_data: ajax_data.qr_submit_data,
						password: ajax_data.qr_password
					};

					content.on("post_load", this_private.on_submitted_post_loader_post_load, cb_data);
					loader.on("complete", this_private.on_submitted_post_loader_complete, cb_data);
				},
				on_submitted_post_loader_post_load: function (event, cb_data) {
					// Check if it's the correct thread
					if (event.post.thread == cb_data.target_thread) {
						if (event.post_context.is_new) {
							// New
							cb_data.new_posts.push(event.post);
						}
					}
				},
				on_submitted_post_loader_complete: function (event, cb_data) {
					// Disable events
					content.off("post_load", this_private.on_submitted_post_loader_post_load);

					// Find post
					var my_post = null;
					for (var i = cb_data.new_posts.length - 1; i >= 0; --i) {
						// Set post
						if (
							my_post == null ||
							this_private.which_post_is_more_accurate.call(cb_data.self, cb_data.submit_data, [my_post, cb_data.new_posts[i]]) == 1
						) {
							my_post = cb_data.new_posts[i];
						}
					}

					// Error checking
					if (my_post == null) {
						// Error
						this_private.submit_error.call(cb_data.self, {
							type: "Post Retrieval Error",
							detail: "Your post could not be found after submission",
							cooldown: "post_retrieval_error"
						});
					}
					else {
						// Mark post as "you"
						content.mark_post_as_me(my_post, cb_data.submit_data, cb_data.password, true);
						this_private.submit_complete.call(cb_data.self, cb_data.submit_data, false);

						// Message
						new xch.Message({
							type: "okay",
							title: "Post Successful",
							text: "Your post was successfully submitted"
						});

						// Go to thread
						if (cb_data.redirect_to_thread) {
							// Go to thread
							location.go_to_new(
								info.create_url.to_post(my_post.thread.board, my_post.thread.id, my_post.id)
							);
						}
						else {
							if (cb_data.self.settings.posting.highlight_after_post) {
								// Highlight
								location.go_to(
									info.create_url.to_post(my_post.thread.board, my_post.thread.id, my_post.id)
								);
							}
						}
					}
				},

				change_fields_readonly: function (readonly) {
					if (this.container == null) return;

					if (readonly) {
						this.fields.attr("readonly", "readonly");
					}
					else {
						this.fields.removeAttr("readonly");
					}
				},

				create_submit_form_data: function (pd) {
					var fd = new FormData();

					// Extra fields
					for (var i = 0; i < this.post_context.data_fields_extra.length; ++i) {
						fd.append(this.post_context.data_fields_extra[i].name, this.post_context.data_fields_extra[i].value);
					}

					// Target
					if (this.target_thread != null) {
						fd.append(this.post_data_names.thread, this.target_thread.id);
						fd.append(this.post_data_names.board, this.target_thread.board);
					}
					else {
						fd.append(this.post_data_names.board, info.board);
					}

					// Fields
					fd.append(this.post_data_names.name, pd.name.value);

					fd.append(this.post_data_names.email, pd.email.value);

					fd.append(this.post_data_names.subject, pd.subject.value);

					if (this.spoiler_available) {
						if (pd.spoiler.value) {
							fd.append(this.post_data_names.spoiler, "on");
						}
					}

					fd.append(this.post_data_names.comment, pd.comment.value);

					if (pd.file.file != null) {
						if ((pd.file.file instanceof File) && pd.file.file.name === pd.file.name) {
							fd.append(this.post_data_names.file, pd.file.file);
						}
						else {
							fd.append(this.post_data_names.file, pd.file.file, pd.file.name);
						}
					}

					fd.append(this.post_data_names.password, this.saved_post_settings.password);

					// Post
					if (this.target_thread != null) {
						fd.append("post", this.post_submit_value.reply);
					}
					else {
						fd.append("post", this.post_submit_value.new_thread);
					}

					return fd;
				},

				on_target_selector_option_click: function (event) {
					event.data.callback_data.self.set_target(event.data.callback_data.thread);
					event.data.option.menu.close();
					return false;
				},
				on_target_selector_close: function (event, self) {
					self.target_selector = null;
				}

			};

			QuickReply.prototype = {

				constructor: QuickReply,

				is_open: function () {
					return (this.container != null && this.popup.is_visible());
				},
				open: function (target_thread) {
					if (this.container == null) {
						this_private.create.call(this);
						this.clear();
					}
					else {
						this.popup.show();
					}

					// Settings
					this.set_target(target_thread);
					this.popup.set_position(this.position.x, this.position.y, true, true);
				},
				close: function () {
					// Close
					if (this.container != null) {
						this.popup.hide();

						// Abort submit
						this_private.submit_abort.call(this);
						this_private.submit_dequeue.call(this);
					}
				},
				clear: function () {
					// Make sure not submitting
					if (this.submitting.active) return;

					// Clear data
					var pd = this_private.create_post_data.call(this);
					this.post_data[this.post_data_id_current] = pd;

					// Clear fields
					if (this.container != null) {
						var o;

						// File
						this.set_file(pd.file.file, pd.file.name);

						// File spoiler
						if (!(o = this.fields.filter(".qr_file_spoiler")).attr("readonly")) o.prop("checked", pd.spoiler.value);

						// Fields
						if (!(o = this.fields.filter(".qr_name")).attr("readonly")) o.val(pd.name.value);
						if (!(o = this.fields.filter(".qr_email")).attr("readonly")) o.val(pd.email.value);
						if (!(o = this.fields.filter(".qr_subject")).attr("readonly")) o.val(pd.subject.value);
						if (!(o = this.fields.filter(".qr_comment")).attr("readonly")) o.val(pd.comment.value);

						this_private.compare_post_name.call(this);
						this_private.compare_post_email.call(this);
					}
				},
				set_target: function (target_thread) {
					// Can only reply to "current"
					if (!info.index) target_thread = null;

					if (target_thread == null) {
						if (info.index) {
							// New
							this.target_thread_is_current = false;
							this.target_thread = null;

							if (this.container != null) {
								this.main_label.attr("target_thread", "new");
								this.main_label_target_link.attr("target_thread", "New Thread");
							}
						}
						else {
							// Current
							this.target_thread_is_current = true;
							this.target_thread = null;

							for (var i = 0; i < content.threads.length; ++i) {
								if (content.threads[i].visible) {
									this.target_thread = content.threads[i];
									break;
								}
							}

							// Delay, in case this happened before threads were parsed
							if (this.target_thread == null && !("on_content_thread_new" in this)) {
								var self = this;
								content.on("thread_new", this.on_content_thread_new = (function (event) {
									self.target_thread = event.thread;
									content.off("thread_new", self.on_content_thread_new);
									self.on_content_thread_new = null;
								}));
							}

							if (this.container != null) {
								this.main_label.attr("target_thread", "current");
								this.main_label_target_link.attr("target_thread", "");
							}
						}
						if (this.container != null) {
							this.main_label_target_link.removeAttr("href");
						}
					}
					else {
						// Thread
						this.target_thread_is_current = false;
						this.target_thread = target_thread;

						if (this.container != null) {
							this.main_label.attr("target_thread", target_thread.id);

							this.main_label_target_link
							.attr("target_thread", ">>" + target_thread.id)
							.attr("href", info.create_url.to_thread(target_thread.board, target_thread.id));
						}
					}
				},
				set_persistency: function (persistent, no_save) {
					this.persistent = persistent;

					if (this.container != null) {
						if (persistent) this.main_label.addClass("persistent");
						else this.main_label.removeClass("persistent");
					}

					// Save persistency
					if (!no_save) {
						this_private.save_persistency.call(this);
					}
				},
				open_target_selector: function () {
					if (this.target_selector == null) {
						// Create thread list
						var options = [];
						if (info.index) {
							options.push({
								html: (
									style.e("a", "thread_selection")
									.text("New Thread")
									.attr("target_thread", "new")
								),
								on: {
									click: {
										callback_data: { self: this, thread: null },
										callback: this_private.on_target_selector_option_click
									}
								}
							});
							for (var i = 0; i < content.threads.length; ++i) {
								if (content.threads[i].visible) {
									options.push({
										html: (
											style.e("a", "thread_selection")
											.attr("href", info.create_url.to_thread(content.threads[i].board, content.threads[i].id))
											.text(">>" + content.threads[i].id)
											.attr("target_thread", content.threads[i].id)
										),
										on: {
											click: {
												callback_data: { self: this, thread: content.threads[i] },
												callback: this_private.on_target_selector_option_click
											}
										}
									});
								}
							}
						}
						else {
							options.push({
								html: (
									style.e("a", "thread_selection")
									.text("Current Thread")
									.attr("target_thread", "current")
								),
								on: {
									click: {
										callback_data: { self: this, thread: null },
										callback: this_private.on_target_selector_option_click
									}
								}
							});
						}

						// Create menu
						this.target_selector = new xch.Menu(this.main_label_target_link, {
							fit_width_min: true,
							keep_on_screen: true,
							fixed_z: null,
							open: "down",
							on: {
								close: {
									callback_data: this,
									callback: this_private.on_target_selector_close
								}
							},
							options: options
						});
					}
				},
				close_target_selector: function () {
					if (this.target_selector != null) {
						this.target_selector.close();
						this.target_selector = null;
					}
				},
				target_selector_is_open: function () {
					return (this.target_selector != null);
				},

				validage_post: function (post_data) {
					var req = (this.target_thread == null ? this.requirements.new_thread : this.requirements.reply);

					if (req.image) {
						if (post_data.file.file == null) {
							return {
								detail: "You must select an image"
							};
						}
					}

					if (req.image_or_comment) {
						if (post_data.file.file == null && post_data.comment.value.trim().length == 0) {
							return {
								detail: "You must fill in the comment field or select a file"
							};
						}
					}

					// Okay
					return null;
				},

				validate_file: function (file, name) {
					var is_file = (file instanceof File);

					if (is_file) name = file.name;

					// Extension checking
					var ext = file.name.split(".");
					ext = (ext.length > 1 ? "." + ext.pop() : "");

					var okay = false;

					for (var i = 0, j, exts; i < this.valid_file_mimes.length; ++i) {
						exts = xch.mime_types[this.valid_file_mimes[i]].extensions;
						for (j = 0; j < exts.length; ++j) {
							// Okay
							if (ext == exts[j]) {
								// Okay
								okay = true;
								i = this.valid_file_mimes.length;
								break;
							}
						}
					}

					// Error
					if (!okay) {
						var okay_types = "";
						for (var i = 0; i < this.valid_file_mimes.length; ++i) {
							if (okay_types.length > 0) okay_types += ", ";
							okay_types += xch.mime_types[this.valid_file_mimes[i]].name;
						}

						return {
							detail: "File must be one of the following types: " + okay_types
						};
					}

					// File size checking
					okay = (file.size <= this.max_file_size);

					// Error
					if (!okay) {
						return {
							detail: "File size cannot exceed " + xch.format_number(this.max_file_size, 1000, 1024, 1, [" B", " KB", " MB"])
						};
					}

					// Okay
					return null;
				},
				set_file: function (file, name) {
					if (this.container == null || this.container.find(".qr_file").attr("readonly")) return;

					var fs = this.container.find(".qr_file_settings");
					var fn = this.container.find(".qr_file_name");

					if (file === null) {
						// Nullify
						this.post_data[this.post_data_id_current].file.name = "";
						this.post_data[this.post_data_id_current].file.file = null;

						// Hide
						fs.addClass("hidden");
						fn.val("").css("padding-right", "");
					}
					else {
						// Validate file
						var err = this.validate_file(file, name);
						if (err != null) {
							// Show error
							new xch.Message({
								type: "error",
								title: "File Error",
								text: err.detail
							});

							// Bad
							return false;
						}

						// Set
						this.post_data[this.post_data_id_current].file.name = (file instanceof File ? file.name : name) || "";
						this.post_data[this.post_data_id_current].file.file = file;

						// Visible
						fs.removeClass("hidden");
						fn.val(this.post_data[this.post_data_id_current].file.name);
						fn.css("padding-right", (fs.outerWidth()) + "px");
					}

					// Okay
					return true;
				},
				set_file_name: function (name) {
					if (this.container == null || this.container.find(".qr_file").attr("readonly") || this.post_data[this.post_data_id_current].file.file == null) {
						return null;
					}

					// Set
					name = xch.ensure_same_mime_type_extension(this.post_data[this.post_data_id_current].file.name, name);
					this.post_data[this.post_data_id_current].file.name = name;

					// Visible
					if (this.container != null) {
						var fn = this.container.find(".qr_file_name");
						fn.val(this.post_data[this.post_data_id_current].file.name);
					}

					// Okay
					return name;
				},

				focus_comment: function () {
					if (this.container == null) return;

					var com = this.container.find(".qr_comment");
					if (com.length > 0) {
						// Selection
						var sel = this.post_data[this.post_data_id_current].comment.selection;

						// Select
						xch.set_selection(com, sel.start, sel.end);
					}
				},

				create_password: function (length) {
					length = length || 8;

					var pass = "";
					var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+";
					for (var i = 0; i < length; ++i) {
						pass += str.charAt(Math.floor(Math.random() * str.length));
					}
					return pass;
				},

				quote: function (post) {
					// Open
					if (!this.is_open()) {
						this.open(post.thread);
						this.clear();
					}
					// Quote
					var com = this.fields.filter(".qr_comment");
					if (com.length > 0 && !com.attr("readonly")) {
						// Selection
						var sel = this.post_data[this.post_data_id_current].comment.selection;
						var start, end;
						if (sel.start < sel.end) {
							start = sel.start;
							end = sel.end;
						}
						else {
							end = sel.start;
							start = sel.end;
						}

						// Quote text
						var quote_text = ">>" + post.id + "\n";

						// Update comment
						var val = com.val();
						var norm = /\r\n/.test(val);
						if (norm) {
							val = val.replace(/\r\n/g, "\n");
						}
						val = val.substr(0, start) + quote_text + val.substr(end);
						if (norm) {
							val = val.replace(/\n/g, "\r\n");
						}
						com.val(val);

						// Select
						xch.set_selection(com, start + quote_text.length, start + quote_text.length);
					}
				}

			};

			return QuickReply;

		})(),


		// Auto-updater
		Updater: (function () {

			var Updater = function (html) {
				var self = this;

				this.thread = null;
				this.first_load = true;

				this.countdown_start_after_parse = true; // false for start after the ajax has completed; true for start after the parsing has completed
				this.countdown = {
					timer: null,
					ready: false,
					time: 0,
					time_last: null,
					last_changed: false,
					last_changed_count: 0
				};

				this.countdown_settings = {
					auto_start: false,
					reset_on_manual_if_auto_enabled: Updater.RESET_NEVER,
					reset_on_manual_if_auto_disabled: Updater.RESET_NEVER,
					reset_on_auto_enable: Updater.RESET_IF_DECREASES,
					initial: 60,
					minimum: 30,
					maximum: 120,
					decrease: {
						count: 1, // how many times a thread change must happen before an amount change
						count_first: 1, // how many times a thread change must happen before the first amount change
						amount: -1, // -1 indicates set to minimum
						manual: true // true = can be changed by a manual update
					},
					increase: {
						count: 2, // how many times a thread must receive no updates before an amount change
						count_first: 4, // how many times a thread must receive no updates before the first amount change
						amount: 15, // increase time by 15 seconds
						manual: false // false = cannot be changed by a manual update
					}
				};

				this.auto_reload_allowed = false;
				this.auto_reload_enabled = false;

				this.loader = null;
				this.loader_parsing = true;
				this.loader_events = null;
				this.manual_updating = true;
				this.thread_changed = false;

				this.totals = {
					posts: 0,
					images: 0
				}
				this.change = {
					added: {
						posts: 0,
						images: 0
					},
					removed: {
						posts: 0,
						images: 0
					}
				};

				this.limits = {
					normal: {
						posts: 500,
						images: 250
					},
					sticky: {
						posts: -1,
						images: 500
					}
				};

				this.position = {
					x: 0,
					y: 0,
					in_header: true,
					anchor: {
						x: 1,
						y: 1
					}
				};

				this.moving = {
					active: false,
					offset: {
						x: 0,
						y: 0
					}
				};

				this.title = {
					prefix: "(%unread) ",
					suffix: ""
				};

				this.status_monitor = {
					total_posts: 0,
					posts_read: 0,
					alive: true,
					nearest_reference_to_me: null
				}

				// Load
				this_private.load_all.call(this);

				// Setup
				if (html != null) {
					this_private.init.call(this, html);
				}
			};

			var save = {
				countdown_settings: function () {
					xch.site_set_value("updater_countdown_settings", this.countdown_settings);

					communication.cross_tab_trigger("updater_save", {
						value: "countdown_settings"
					});
				},
				title: function () {
					xch.site_set_value("updater_title", this.title);

					communication.cross_tab_trigger("updater_save", {
						value: "title"
					});
				},
				position: function () {
					xch.site_set_value("updater_position", this.position);

					communication.cross_tab_trigger("updater_save", {
						value: "position"
					});
				}
			};
			var load = {
				countdown_settings: function (apply) {
					var set = xch.site_get_value("updater_countdown_settings", this.countdown_settings);
					if (set !== this.countdown_settings) {
						// Merge
						xch.merge_settings(set, this.countdown_settings, false);

						if (apply) {
							// ...
						}
					}
				},
				title: function (apply) {
					var set = xch.site_get_value("updater_title", this.title);
					if (set !== this.title) {
						// Merge
						xch.merge_settings(set, this.title, false);

						if (apply) {
							// ...
						}
					}
				},
				position: function (apply) {
					var pos = xch.site_get_value("updater_position", this.position);
					if (pos !== this.position) {
						var in_header = this.position.in_header;

						xch.merge_settings(pos, this.position, false);

						if (apply) {
							if (this.position.in_header != in_header) {
								this.position.in_header = in_header;
								this.set_in_header(!in_header, true, true);
							}
							this.set_position(this.position.x, this.position.y, true, true);
						}
					}
				}
			};

			var this_private = {

				load_all: function () {
					for (var key in load) {
						load[key].call(this, false);
					}
				},

				on_cross_tab_save: function (event, self) {
					if (event.value in load) {
						load[event.value].call(self, true);
					}
				},

				on_content_thread_new: function (event, self) {
					this_private.set_thread.call(self, event.thread);
					content.off("thread_new", this_private.on_content_thread_new);
				},
				on_content_thread_load: function (event, self) {
					if (event.thread == self.thread && self.first_load) {
						// Completed
						this_private.thread_loaded_first.call(self);
					}
				},
				on_content_thread_delete: function (event, self) {
					if (event.thread == self.thread) {
						// 404
						this_private.thread_deleted.call(self);
					}
				},
				on_content_thread_update: function (event, self) {
					if (event.thread == self.thread) {
						self.thread_changed = true;

						// Thread has been updated
						this_private.thread_updated.call(self, event.state_old);
					}
				},
				on_content_post_load: function (event, self) {
					if (event.post.thread == self.thread) {
						if (event.post_context.is_new) {
							self.thread_changed = true;

							// Update totals
							this_private.increment_value.call(self, self.totals, false, 1);
							// Update added
							if (!self.first_load) this_private.increment_value.call(self, self.change.added, false, 1);

							// Image?
							if (event.post.image != null && event.post.image.url != null) {
								// Update totals
								this_private.increment_value.call(self, self.totals, true, 1);
								// Update added
								if (!self.first_load) this_private.increment_value.call(self, self.change.added, true, 1);
							}

							// Append
							if (!event.context.is_new) {
								event.post.insert_into_thread();
							}

							// Update title
							var favicon_change = false;
							if (self.status_monitor.nearest_reference_to_me == null && event.post.references_me()) {
								self.status_monitor.nearest_reference_to_me = event.post;
								favicon_change = true;
							}

							var any_unread = (self.status_monitor.total_posts == self.status_monitor.posts_read);
							this_private.update_status_add_post.call(self, event.post);
							this_private.update_status_title.call(self);
							if (any_unread != (self.status_monitor.total_posts == self.status_monitor.posts_read) || favicon_change) {
								this_private.update_status_favicon.call(self);
							}
						}
						else if (event.updated) {
							if (event.update_data.image_deleted) {
								self.thread_changed = true;
								this_private.increment_value.call(self, self.totals, true, -1);
								this_private.increment_value.call(self, self.change.removed, true, 1);
							}
						}
					}
				},
				on_content_post_delete: function (event, self) {
					if (event.post.thread == self.thread) {
						self.thread_changed = true;
						this_private.increment_value.call(self, self.totals, false, -1);
						this_private.increment_value.call(self, self.change.removed, false, 1);
					}
				},

				on_window_scroll: function (event) {
					var ev_data = event.data;
					// Short timer
					if (ev_data.timer_short !== null) {
						clearTimeout(ev_data.timer_short);
					}
					ev_data.timer_short = setTimeout(function () {
						ev_data.timer_short = null;
						if (ev_data.timer_long !== null) {
							clearTimeout(ev_data.timer_long);
							ev_data.timer_long = null;
						}
						this_private.update_status_posts_read.call(ev_data.self);
					}, 100);

					// Long timer
					if (ev_data.timer_long === null) {
						ev_data.timer_long = setTimeout(function () {
							ev_data.timer_long = null;
							if (ev_data.timer_short !== null) {
								clearTimeout(ev_data.timer_short);
								ev_data.timer_short = null;
							}
							this_private.update_status_posts_read.call(ev_data.self);
						}, 1000);
					}
				},

				init: function (html) {
					var s_enabled, s_countdown, s_progress_bar, s_totals;

					content.floating_container.append(
						(this.container = style.e("div", "updater"))
						.append(
							style.e("div", "updater_background")
						)
						.append( //{ Small
							style.e("div", "updater_small")
							.append(
								(s_enabled = style.checkbox(false, "small"))
								.addClass("updater_small_item enabled")
								.attr("title", "Auto reload")
							)
							.append(
								style.e("span", "updater_difference updater_small_item hidden")
								.append(
									style.e("span", "updater_difference_added updater_counts hidden")
									.append(
										style.e("span", "updater_counts_inner")
										.append(
											style.e("span", "updater_counts_posts hidden")
										)
										.append(
											style.e("span", "updater_counts_images hidden")
										)
									)
								)
								.append(
									style.e("span", "updater_difference_removed updater_counts hidden")
									.append(
										style.e("span", "updater_counts_inner")
										.append(
											style.e("span", "updater_counts_posts hidden")
										)
										.append(
											style.e("span", "updater_counts_images hidden")
										)
									)
								)
							)
							.append(
								(this.small_countdown = style.e("a", "updater_countdown updater_small_item"))
								.attr("title", "Update now")
								.append(
									(s_progress_bar = style.e("div", "updater_progress"))
									.append(
										style.e("div", "updater_progress_inner")
									)
								)
								.append(
									(this.small_countdown_text = style.e("span", "updater_countdown_suffix"))
								)
							)
							.append(
								(s_totals = style.e("span", "updater_counts updater_totals updater_small_item hidden"))
								.append(
									style.e("span", "updater_counts_inner")
									.append(
										style.e("span", "updater_counts_posts")
									)
									.append(
										style.e("span", "updater_counts_images hidden")
									)
								)
							)
						) //}
					);

					// More data
					this.progress_bars = s_progress_bar;

					// Status updates
					this_private.set_value.call(this, this.totals, false, 0);
					this_private.set_value.call(this, this.totals, true, 0);
					this_private.reset_counts.call(this);
					this_private.update_countdown_status.call(this);

					// Events
					var data = { self: this };
					var g_data = { self: this, grabber: s_totals, header_container: content.header.container, header_options: content.header.container.find(".header_options") };

					s_enabled.find("input").on("change", data, this_private.on_enabler_click);
					this.small_countdown.on("click", data, this_private.on_countdown_click);

					s_totals.on("mousedown", g_data, this_private.on_grabber_mousedown);
					$(window)
					.on("mouseup", g_data, this_private.on_window_mouseup)
					.on("mousemove", g_data, this_private.on_window_mousemove)
					.on("resize", { self: this, timer: null }, this_private.on_window_resize);

					// Position
					if (this.position.in_header) {
						this.position.in_header = false;
						this.set_in_header(true, true, true);
					}
					this.set_position(this.position.x, this.position.y, true, true);

					// Title/favicon updating
					this_private.update_status_title.call(this);
					this_private.update_status_favicon.call(this);

					// Event listeners
					content.on("thread_new", this_private.on_content_thread_new, this);
					content.on("thread_load", this_private.on_content_thread_load, this);
					content.on("thread_delete", this_private.on_content_thread_delete, this);
					content.on("thread_update", this_private.on_content_thread_update, this);
					content.on("post_load", this_private.on_content_post_load, this);
					content.on("post_delete", this_private.on_content_post_delete, this);

					var data = { self: this, timer_short: null, timer_long: null };
					$(window).on("scroll", data, this_private.on_window_scroll);

					communication.cross_tab_on("updater_save", this_private.on_cross_tab_save, this);
				},

				on_enabler_click: function (event) {
					if (event.data.self.auto_enabled()) {
						this_private.disable_auto.call(event.data.self);
					}
					else {
						this_private.enable_auto.call(event.data.self);
					}

					// Change
					var en = event.data.self.auto_enabled();
					if (en != $(this).is(":checked")) {
						$(this).prop("checked", en);
					}
				},
				on_countdown_click: function (event) {
					if (event.which != 1) return true;

					if (!event.data.self.first_load) {
						event.data.self.update(true);
					}

					return false;
				},
				on_grabber_mousedown: function (event) {
					if (event.which != 1) return true;

					event.data.self.moving.active = true;

					var o = event.data.self.container.offset();
					event.data.self.moving.offset.x = event.pageX - o.left;
					event.data.self.moving.offset.y = event.pageY - o.top;

					return false;
				},
				on_window_mouseup: function (event) {
					if (event.data.self.moving.active) {
						event.data.self.moving.active = false;

						// Save position
						save.position.call(event.data.self);
					}
				},
				on_window_mousemove: function (event) {
					if (event.data.self.moving.active) {
						var x = event.pageX - event.data.self.moving.offset.x - $(this).scrollLeft();
						var y = event.pageY - event.data.self.moving.offset.y - $(this).scrollTop();

						// Check if hovering over the header
						var o1 = event.data.header_container.offset();
						var o2 = event.data.header_options.offset();
						var w = event.data.header_options.width();
						var h = event.data.header_container.height();
						var self_w = (event.data.self.is_in_header() ? 0 : event.data.self.container.outerWidth());
						event.data.self.set_in_header(
							event.pageY >= o1.top && event.pageY < o1.top + h &&
							event.pageX >= o2.left - self_w && event.pageX < o2.left + w,
							true,
							true
						);

						// Position
						event.data.self.set_position(x, y, false, true);
					}
				},
				on_window_resize: function (event) {
					var ev_data = event.data;
					if (ev_data.self.container != null) {
						ev_data.self.set_position(ev_data.self.position.x, ev_data.self.position.y, true, true);

						// Save
						if (ev_data.timer !== null) {
							clearTimeout(ev_data.timer);
						}
						ev_data.timer = setTimeout(function () {
							ev_data.timer = null;
							// Save position
							save.position.call(ev_data.self);
						}, 100);
					}
				},

				set_thread: function (thread) {
					this.thread = thread;

					if (this.thread.locked) {
						this_private.disallow_auto.call(this);
					}
					else {
						this_private.allow_auto.call(this);
					}
				},

				thread_loaded_first: function () {
					// Completed initial load
					this.first_load = false;
					this.loader_parsing = false;

					// Start timer if threads should start off with auto reload
					if (this.countdown_settings.auto_start) {
						this_private.enable_auto.call(this);
					}
				},
				thread_deleted: function () {
					// Disable
					this_private.disable_auto.call(this);
					this_private.disallow_auto.call(this);

					// Signal 404'd
					new xch.Message({
						type: "info",
						title: "Thread 404'd",
						text: "This thread no longer exists"
					});

					// Change title
					this.status_monitor.alive = false;
					this_private.update_status_favicon.call(this);
				},
				thread_updated: function (state_old) {
					if (state_old.locked != this.thread.locked) {
						if (this.thread.locked) {
							// Disable
							this_private.disable_auto.call(this);
							this_private.disallow_auto.call(this);

							// Message
							new xch.Message({
								type: "info",
								title: "Thread Locked",
								text: "This thread is now locked"
							});
						}
						else {
							this_private.allow_auto.call(this);

							// Message
							new xch.Message({
								type: "info",
								title: "Thread Unlocked",
								text: "This thread is now unlocked"
							});
						}
					}
					if (state_old.sticky != this.thread.sticky) {
						// Update totals (limits may have been changed)
						this_private.set_value.call(this, this.totals, false, this.totals.posts);
						this_private.set_value.call(this, this.totals, true, this.totals.images);

						// Message
						if (this.thread.sticky) {
							new xch.Message({
								type: "info",
								title: "Thread Stickied",
								text: "This thread is now a sticky"
							});
						}
						else {
							new xch.Message({
								type: "info",
								title: "Thread Unstickied",
								text: "This thread is no longer a sticky"
							});
						}
					}
				},

				enable_auto: function () {
					// Ignore if not allowed or already enabled
					if (!this.auto_reload_allowed || this.auto_reload_enabled || this.first_load) return;

					// Enable
					this.auto_reload_enabled = true;

					// Reset countdown settings
					this_private.reset_countdown_time.call(this, this.countdown_settings.reset_on_auto_enable);

					// Start timer if no loader is running
					if (this.loader == null || (this.loader_parsing && !this.countdown_start_after_parse)) {
						this_private.start_countdown_timer.call(this);
					}

					// Checked
					this.container.find(".checkbox.enabled input").prop("checked", true);
				},
				disable_auto: function () {
					if (!this.auto_reload_enabled) return;

					// Disable
					this.auto_reload_enabled = false;

					// Stop timer
					this_private.stop_countdown_timer.call(this);

					// Unchecked
					this.container.find(".checkbox.enabled input").prop("checked", false);
				},
				allow_auto: function () {
					this.auto_reload_allowed = true;

					this.container.find(".checkbox.enabled").removeClass("hidden");
				},
				disallow_auto: function () {
					this.auto_reload_allowed = false;

					this.container.find(".checkbox.enabled").addClass("hidden");

					// Disable
					this_private.disable_auto.call(this);
				},

				start_countdown_timer: function () {
					// Clear timer
					if (this.countdown.timer !== null) {
						clearInterval(this.countdown.timer);
					}

					// Set the timer
					if (this.countdown.time_last === null) {
						this_private.reset_countdown_time.call(this, Updater.RESET_ALWAYS);
					}
					this.countdown.time = this.countdown.time_last;
					this.countdown.ready = false;

					// Begin the timer
					var self = this;
					this.countdown.timer = setInterval(function () {
						this_private.countdown_timer_tick.call(self);
					}, 1000);

					// Update status
					this_private.update_countdown_status.call(this);
				},
				stop_countdown_timer: function () {
					// Clear timer
					if (this.countdown.timer !== null) {
						clearInterval(this.countdown.timer);
						this.countdown.timer = null;
					}

					this.countdown.time = 0;
					this.countdown.ready = false;

					// Update status
					this_private.update_countdown_status.call(this);
				},
				countdown_timer_tick: function () {
					// Time change
					if (--this.countdown.time <= 0) {
						// Clear timer
						if (this.countdown.timer !== null) {
							clearInterval(this.countdown.timer);
							this.countdown.timer = null;
						}

						// Update
						this.countdown.ready = true;
						this.update(false);
					}
					else {
						this.small_countdown.addClass("counting");
						this.small_countdown_text.text(this.countdown.time);
					}
				},
				update_countdown_time: function () {
					var cds = (this.thread_changed ? this.countdown_settings.decrease : this.countdown_settings.increase);

					// Change count
					if (this.countdown.last_changed == this.thread_changed) {
						++this.countdown.last_changed_count;
					}
					else {
						this.countdown.last_changed = this.thread_changed;
						this.countdown.last_changed_count = 1;
					}

					// Change timer
					if (!this.manual_updating || cds.manual) {
						if (this.countdown.last_changed_count >= cds.count_first) {
							var c = this.countdown.last_changed_count - cds.count_first;
							if ((c % cds.count) == 0) {
								// Change
								if (this.thread_changed) {
									// Decrease
									this.countdown.time_last = (cds.amount < 0 ?
										this.countdown_settings.minimum :
										Math.max(this.countdown_settings.minimum, this.countdown.time_last - cds.amount));
								}
								else {
									// Increase
									this.countdown.time_last = (cds.amount < 0 ?
										this.countdown_settings.maximum :
										Math.min(this.countdown_settings.maximum, this.countdown.time_last + cds.amount));
								}
							}
						}
					}
				},
				reset_countdown_time: function (reset_setting) {
					// Ignore
					if (reset_setting == Updater.RESET_NEVER) return;

					// Check for change
					var change, target_time = this.countdown_settings.initial;

					if (this.countdown.time_last === null) {
						change = true;
					}
					else {
						if (target_time > this.countdown.time_last) {
							change = ((reset_setting & Updater.RESET_IF_INCREASES) != 0);
						}
						else {
							change = ((reset_setting & Updater.RESET_IF_DECREASES) != 0);
						}
					}

					// Set
					if (change) {
						this.countdown.time_last = target_time;
						this.countdown.last_changed = false;
						this.countdown.last_changed_count = 0;
					}
				},

				update_countdown_status: function () {
					if (this.auto_reload_enabled) {
						if (this.countdown.timer === null) {
							// Ajax or parsing
							this.small_countdown.removeClass("counting");
							this.small_countdown_text.text("...");
						}
						else {
							// Timer
							this.small_countdown.addClass("counting");
							this.small_countdown_text.text(this.countdown.time);
						}
					}
					else {
						if (this.loader == null) {
							// Not activated
							this.small_countdown.removeClass("counting");
							this.small_countdown_text.text("up");
						}
						else {
							// Loading something
							this.small_countdown.removeClass("counting");
							this.small_countdown_text.text("...");
						}
					}
				},

				reset_counts: function () {
					this_private.set_value.call(this, this.change.added, false, 0);
					this_private.set_value.call(this, this.change.added, true, 0);
					this_private.set_value.call(this, this.change.removed, false, 0);
					this_private.set_value.call(this, this.change.removed, true, 0);
				},

				create_loader_events: function () {
					var self = this;
					this.loader_events = {
						load: function (event) { return this_private.on_loader_load.call(self, event, this); },
						error: function (event) { return this_private.on_loader_error.call(self, event, this); },
						complete: function (event) { return this_private.on_loader_complete.call(self, event, this); },
						progress: function (event) { return this_private.on_loader_progress.call(self, event, this); },
						status_change: function (event) { return this_private.on_loader_status_change.call(self, event, this); }
					};
				},
				create_loader: function () {
					// Clear error, indicate loading
					this.small_countdown.addClass("loading").removeClass("error");

					// Initial settings for load
					this.thread_changed = false;
					this.loader_parsing = false;

					// Create
					this_private.create_loader_events.call(this);
					this.loader = xch.Loader.get_thread(
						this.thread.board,
						this.thread.id,
						null,
						this.loader_events
					);
				},
				bind_to_loader: function (loader) {
					var self = this;

					// Clear error, indicate loading
					this.small_countdown.addClass("loading").removeClass("error");

					// Initial settings for load
					this.thread_changed = false;
					this.loader_parsing = false;

					// Bind
					this_private.create_loader_events.call(this);
					this.loader = loader;

					// Events
					for (var event in this.loader_events) {
						this.loader.on(event, this.loader_events[event]);
					}
				},

				on_loader_load: function (event, loader) {
					// Now parsing
					this.loader_parsing = true;

					// Timer
					if (this.auto_reload_enabled && !this.countdown_start_after_parse) {
						// Start reload timer
						this_private.start_countdown_timer.call(this);
					}

					// Update status
					this_private.update_countdown_status.call(this);
				},
				on_loader_error: function (event, loader) {
					// Add error class
					this.small_countdown.addClass("error");

					// Signal error
					if (this.error_message != null) {
						this.error_message.close();
					}
					this.error_message = new xch.Message({
						type: "error",
						title: "Updater Error",
						text: event.reason,
						on: {
							close: {
								callback_data: this,
								callback: this_private.on_error_message_close
							}
						}
					});
				},
				on_loader_complete: function (event, loader) {
					// Nullify loader
					this.loader = null;
					this.loader_parsing = false;
					this.loader_events = null;

					// Progress bar
					this.small_countdown.removeClass("loading");

					// Next
					if (this.auto_reload_enabled) {
						// Update timing settings here
						this_private.update_countdown_time.call(this);

						if (this.countdown.ready) {
							// Next update
							this.update();
						}
						else {
							// Start reload timer
							this_private.start_countdown_timer.call(this);
						}
					}

					// Update status
					this_private.update_countdown_status.call(this);
				},
				on_loader_progress: function (event, loader) {
					// Update percent
					this.progress_bars.children().css("width", (event.ratio * 100).toFixed(2) + "%");
				},
				on_loader_status_change: function (event, loader) {
					// Update styling of the progress bars
					switch (loader.status) {
						case xch.Loader.status.NOT_RUNNING:
						case xch.Loader.status.AJAX:
						case xch.Loader.status.ERROR:
						{
							this.progress_bars
							.removeClass("ajax parse")
							.children()
								.removeClass("parse")
								.addClass("ajax")
								.css("width", (loader.ajax_progress * 100).toFixed() + "%");
						}
						break;
						case xch.Loader.status.THREAD_DELETED:
						case xch.Loader.status.PARSE_WAITING:
						case xch.Loader.status.PARSING:
						{
							this.progress_bars
							.removeClass("parse")
							.addClass("ajax")
							.children()
								.removeClass("ajax")
								.addClass("parse")
								.css("width", (loader.parse_progress * 100).toFixed() + "%");
						}
						break;
						case xch.Loader.status.COMPLETE:
						{
							this.progress_bars
							.removeClass("ajax")
							.addClass("parse")
							.children()
								.removeClass("ajax parse")
								.css("width", "0%");
						}
						break;
					}
				},

				on_error_message_close: function (event, self) {
					self.error_message = null;
				},

				increment_value: function (target, images, count) {
					this_private.set_value.call(this, target, images, target[images ? "images" : "posts"] + count);
				},
				set_value: function (target, images, value) {
					// Set value
					if (value < 0) value = 0;
					target[images ? "images" : "posts"] = value;

					// Update display
					if (target == this.totals) {
						var limits = this.limits[(this.thread && this.thread.sticky) ? "sticky" : "normal"];

						var c1 = this.container.find(".updater_totals");
						c1.removeClass("hidden");
						if (images) {
							var c2 = c1.find(".updater_counts_images");
							if (value > 0) c2.removeClass("hidden");
							else c2.addClass("hidden");
							c2.text(value);
							if (value > limits.images && limits.images > 0) {
								c2.addClass("limit");
							}
							else {
								c2.removeClass("limit");
							}
						}
						else {
							var c2 = c1.find(".updater_counts_posts");
							c2.text(value);

							if (value > limits.posts && limits.posts > 0) {
								c2.addClass("limit");
							}
							else {
								c2.removeClass("limit");
							}
						}

						// Alt title
						var alt_title = "Thread contains " +
							(target.posts == 0 ? "no" : target.posts) + " post" + (target.posts == 1 ? "" : "s") +
							" and " + (target.images == 0 ? "no" : target.images) + " image" + (target.images == 1 ? "" : "s");

						if (target.posts > limits.posts && limits.posts > 0) alt_title += " / Bump limit reached";
						if (target.images > limits.images && limits.images > 0) alt_title += " / Image limit reached";

						c1.attr("title", alt_title);
					}
					else {
						var c1 = this.container.find(".updater_difference");
						if (
							value > 0 ||
							this.change.added.posts > 0 || this.change.added.images > 0 ||
							this.change.removed.posts > 0 || this.change.removed.images > 0
						) {
							c1.removeClass("hidden");
						}
						else {
							c1.addClass("hidden");
						}

						var c2 = c1.find(target == this.change.added ? ".updater_difference_added" : ".updater_difference_removed");
						if (target.posts > 0 || target.images > 0) {
							c2.removeClass("hidden");
						}
						else {
							c2.addClass("hidden");
						}

						var c3 = c2.find(images ? ".updater_counts_images" : ".updater_counts_posts");
						if (value > 0) {
							c3.removeClass("hidden");
						}
						else {
							c3.addClass("hidden");
						}
						c3.text(value);

						// Alt title
						var alt_title = "";
						if (target.posts > 0) {
							alt_title += target.posts + " post" + (target.posts == 1 ? "" : "s");
						}
						if (target.images > 0) {
							alt_title += (target.posts == 0 ? "" : " and ") + target.images + " image" + (target.images == 1 ? "" : "s");
						}
						if (alt_title.length > 0) {
							alt_title += (target == this.change.added ? " added" : " removed") + " in the last update";
						}

						c2.attr("title", alt_title);
					}
				},

				update_status_posts_read: function () {
					// Ignore
					if (this.thread == null) return;

					// Check for posts read
					var win = $(window);
					var area = content.header.get_screen_area();
					//area.left += win.scrollLeft();
					area.top += win.scrollTop();
					var bottom = area.top + area.height;

					var p_count = this.thread.posts.length;
					var c, offset, height, i, change = 0, me_ref_change = false;
					for (i = this.status_monitor.posts_read; i < p_count; ++i) {
						// Not loaded yet
						if (!this.thread.posts[i].loaded()) break;

						c = this.thread.posts[i].instances[0].container.children(".post");
						offset = c.offset();
						height = c.outerHeight();

						if (offset.top + height <= bottom) {
							if (this.status_monitor.nearest_reference_to_me == this.thread.posts[i]) {
								// Change
								this.status_monitor.nearest_reference_to_me = null;
								me_ref_change = true;
							}
							++change
						}
						else {
							break;
						}
					}
					if (change > 0) {
						// To-me changing
						if (me_ref_change) {
							for (; i < p_count; ++i) {
								// Not loaded yet
								if (!this.thread.posts[i].loaded()) break;

								if (this.thread.posts[i].references_me()) {
									// New post referencing me
									this.status_monitor.nearest_reference_to_me = this.thread.posts[i];
									me_ref_change = false;
									break;
								}
							}
						}

						// Update
						var any_unread = (this.status_monitor.total_posts == this.status_monitor.posts_read);
						this.status_monitor.posts_read += change;
						this_private.update_status_title.call(this);
						// Favicon update
						if (any_unread != (this.status_monitor.total_posts == this.status_monitor.posts_read) || me_ref_change) {
							this_private.update_status_favicon.call(this);
						}
					}
				},

				update_status_add_post: function (post) {
					++this.status_monitor.total_posts;
					if (this.status_monitor.posts_read == 0) {
						// Indicate OP as read
						++this.status_monitor.posts_read;
					}

					// Check if post is read
					if (this.status_monitor.posts_read == post.t_id) {
						this_private.update_status_posts_read.call(this);
					}
				},

				update_status_favicon: function () {
					var favicon = info.favicons;
					favicon = (this.status_monitor.alive ? favicon.alive : favicon.dead);

					if (this.status_monitor.posts_read == this.status_monitor.total_posts) {
						// Default
						favicon = favicon.standard;
					}
					else {
						// Unread
						favicon = (this.status_monitor.nearest_reference_to_me == null ? favicon.new_post : favicon.new_post_to_me);
					}

					// Set favicon
					content.set_favicon(favicon);
				},
				update_status_title: function () {
					// Title formatting
					var data = {
						read: this.status_monitor.posts_read,
						replies: Math.max(0, this.status_monitor.total_posts - 1),
						unread: 0
					};
					data.unread = data.replies - (data.read - 1);
					var pre = xch.format_string(this.title.prefix, data);
					var suf = xch.format_string(this.title.suffix, data);
					content.format_title(null, pre, suf);
				}

			};

			Updater.prototype = {

				constructor: Updater,

				update: function (manual) {
					if (this.loader != null) return;

					// Reset countdown settings
					if (manual) {
						if (this.auto_enabled()) {
							this_private.reset_countdown_time.call(this, this.countdown_settings.reset_on_manual_if_auto_enabled);
						}
						else {
							this_private.reset_countdown_time.call(this, this.countdown_settings.reset_on_manual_if_auto_disabled);
						}
					}

					// Clear timer
					if (this.countdown.timer !== null) {
						clearInterval(this.countdown.timer);
						this.countdown.timer = null;
					}
					this.countdown.ready = false;

					// Manual
					this.manual_updating = manual;

					// Count reset
					this_private.reset_counts.call(this);

					// Create loader
					this_private.create_loader.call(this);

					// Run
					this.loader.run();

					// Update status
					this_private.update_countdown_status.call(this);
				},
				update_from_loader: function (loader, pre_run_callback) {
					// Negate other loader
					if (this.loader != null) {
						// Remove events
						if (this.loader_events != null) {
							for (var event in this.loader_events) {
								this.loader.off(event, this.loader_events[event]);
							}
							this.loader_events = null;
						}

						// Ignore
						this.loader = null;
					}

					// Clear timer
					if (this.countdown.timer !== null) {
						clearInterval(this.countdown.timer);
						this.countdown.timer = null;
					}
					this.countdown.ready = false;

					// Manual
					this.manual_updating = true;

					// Count reset
					this_private.reset_counts.call(this);

					// Bind to loader
					this_private.bind_to_loader.call(this, loader);

					// Function call
					if (pre_run_callback) {
						pre_run_callback.call(this, this, loader);
					}

					// Run
					this.loader.run();

					// Update status
					this_private.update_countdown_status.call(this);
				},

				auto_enabled: function () {
					return this.auto_reload_enabled;
				},

				set_position: function (x, y, no_anchor_updates, no_save) {
					this.position.x = x;
					this.position.y = y;

					if (this.container != null) {
						if (this.position.in_header) {
							this.container.css({
								left: "",
								top: "",
								bottom: "",
								right: ""
							});
						}
						else {
							var bounds = content.header.get_screen_area();
							var size = {
								w: this.container.outerWidth(),
								h: this.container.outerHeight()
							};

							// Bounds updating
							if (!no_anchor_updates || this.position.anchor.x == 0) {
								this.position.anchor.x = 0;
								if (x + size.w > bounds.left + bounds.width - settings.popup.anchor_border_size) {
									this.position.anchor.x = 1;
								}
								if (x < bounds.left + settings.popup.anchor_border_size) {
									this.position.anchor.x = -1;
								}
							}

							if (!no_anchor_updates || this.position.anchor.y == 0) {
								this.position.anchor.y = 0;
								if (y + size.h > bounds.top + bounds.height - settings.popup.anchor_border_size) {
									this.position.anchor.y = 1;
								}
								if (y < bounds.top + settings.popup.anchor_border_size) {
									this.position.anchor.y = -1;
								}
							}

							// Position
							if (this.position.anchor.x == 0) {
								this.container.css("left", x + "px").css("right", "");
							}
							else if (this.position.anchor.x == -1) {
								this.container.css("left", bounds.left + "px").css("right", "");
							}
							else {
								this.container.css("right", ($(window).width() - (bounds.left + bounds.width)) + "px").css("left", "");
							}

							if (this.position.anchor.y == 0) {
								this.container.css("top", y + "px").css("bottom", "");
							}
							else if (this.position.anchor.y == -1) {
								this.container.css("top", bounds.top + "px").css("bottom", "");
							}
							else {
								this.container.css("bottom", ($(window).height() - (bounds.top + bounds.height)) + "px").css("top", "");
							}
						}
					}

					if (!no_save) {
						save.position.call(this);
					}
				},

				set_in_header: function (in_header, dont_set_position, no_save) {
					if (this.position.in_header != in_header) {
						this.position.in_header = in_header;

						if (in_header) {
							content.header.container.find(".header_options").prepend(this.container);
							this.container.addClass("header_option no_link embedded");
							if (!dont_set_position) this.set_position(this.position.x, this.position.y, true, true);
						}
						else {
							content.floating_container.append(this.container);
							this.container.removeClass("header_option no_link embedded");
							if (!dont_set_position) this.set_position(this.position.x, this.position.y, true, true);
						}

						if (!no_save) {
							save.position.call(this);
						}
						return true;
					}
					return false;
				},

				is_in_header: function () {
					return this.position.in_header;
				},

				get_change_apply_functions: function (change_list) {
					return {
						save: {
							"countdown_settings": save.countdown_settings,
							"title": save.title
						},
						apply: {
						}
					};
				}


			};

			Updater.RESET_NEVER = 0;
			Updater.RESET_IF_INCREASES = 1;
			Updater.RESET_IF_DECREASES = 2;
			Updater.RESET_ALWAYS = 3;

			return Updater;

		})(),

		// Loader
		Loader: (function () {

			var Loader = function (board, thread) {
				this.board = board;
				this.thread = thread;
				this.posts_total = 0;
				this.posts_loaded = 0;
				this.ajax_progress = 0.0;
				this.parse_progress = 0.0;
				this.status = Loader.status.NOT_RUNNING;
				this.context = null;

				this.source = null;

				this.events = {
					load: [], // ajax-load
					progress: [], // take into account ajax-progress and parsing-progress
					error: [], // ajax-error

					thread_ready: [], // indicate a new thread
					thread_load: [], // thread loaded
					thread_delete: [], // thread 404
					post_load: [], // post loaded
					complete: [], // parsing complete

					status_change: [] // status change
				};
			};

			var this_private = {

				html_load: function (html) {
					// Status
					this_private.status_change.call(this, Loader.status.PARSE_WAITING);

					// Create context
					var new_info = new xch.Information(null, html);
					this.context = new xch.Content.Context({
						info: new_info,
						html: html,
						post_queue: (settings.post.fetching.parsing_group.use_single ? content.main_context.post_queue : null),
						html_target: content.main_context.html,
						stylize: true,
						is_new: false,
						loader: this
					});

					// Enable events
					content.on("thread_ready", this_private.on_thread_ready, this);
					content.on("thread_load", this_private.on_thread_load, this);
					content.on("thread_delete", this_private.on_thread_delete, this);
					content.on("post_load", this_private.on_post_load, this);
					content.on("complete", this_private.on_complete, this);

					// Parse
					content.parse(this.context);
				},

				complete: function () {
					// Trigger complete
					if (this.status != Loader.status.COMPLETE) {
						// Error
						this_private.trigger.call(this, "complete", {
							good: false
						});
					}

					// Remove from active
					for (var i = 0, j = Loader.active_loaders.length; i < j; ++i) {
						if (this == Loader.active_loaders[i]) {
							Loader.active_loaders.splice(i, 1);
							break;
						}
					}

					// Clear events
					this.events = [];

					// Disable event listening
					if (this.context != null) {
						content.off("thread_ready", this_private.on_thread_ready);
						content.off("thread_load", this_private.on_thread_load);
						content.off("thread_delete", this_private.on_thread_delete);
						content.off("post_load", this_private.on_post_load);
						content.off("complete", this_private.on_complete);
						this.context = null;
					}

					// Not running
					this.new_content = null;
				},

				on_thread_ready: function (event, self) {
					if (event.context != self.context) return;

					// Increment total post count
					self.posts_total += event.thread_context.posts_total;

					// Trigger thread_new event
					this_private.trigger.call(self, "thread_ready", {
						thread: event.thread
					});
				},
				on_thread_load: function (event, self) {
					if (event.context != self.context) return;

					// Trigger thread_new event
					this_private.trigger.call(self, "thread_load", {
						thread: event.thread
					});
				},
				on_thread_delete: function (event, self) {
					if (event.context != self.context) return;

					// Trigger thread_new event
					this_private.trigger.call(self, "thread_delete", {
						thread: event.thread
					});
				},
				on_post_load: function (event, self) {
					if (event.context != self.context) return;

					// Change status
					if (self.status = Loader.status.PARSE_WAITING) {
						this_private.status_change.call(self, Loader.status.PARSING);
					}

					// Increment posts loaded
					self.posts_loaded += 1;

					// Progress
					self.parse_progress = self.posts_loaded / self.posts_total;
					this_private.trigger.call(self, "progress", {
						type: "parse",
						ratio: self.parse_progress,
						loaded: self.posts_loaded,
						total: self.posts_total
					});
					// Trigger event
					this_private.trigger.call(self, "post_load", {
						post: event.post
					});
				},
				on_complete: function (event, self) {
					if (event.context != self.context) return;

					// Event
					this_private.trigger.call(self, "complete", {
						good: true
					});
					// Status
					this_private.status_change.call(self, Loader.status.COMPLETE);
					// Complete
					this_private.complete.call(self);
				},

				ajax_on_load: function (event) {
					// Full ajax progress
					event.data.loader.ajax_progress = 1.0;

					// Events
					if (event.okay) {
						// Get html
						var html;
						if (event.data.loader.source == null) {
							// Parse response
							html = xch.string_to_dom(event.response);
						}
						else {
							html = event.data.loader.source.html;
							event.data.loader.source.html = null;
						}
						// Check
						if (html != null && (html = $(html)).length > 0) {
							var error;
							if ((error = content.check_html_for_error(html)) !== null) {
								// Remove last "."
								if (error[error.length - 1] == "." && error.substr(-2) != "..") {
									error = error.substr(0, error.length - 1);
								}

								// Response error
								this_private.trigger.call(event.data.loader, "error", {
									type: "response",
									reason: error
								});
								this_private.status_change.call(event.data.loader, Loader.status.ERROR);
								this_private.complete.call(event.data.loader);
							}
							else {
								// Okay
								this_private.trigger.call(event.data.loader, "load", {
									html: html
								});
								// Parse
								this_private.html_load.call(event.data.loader, html);
							}
						}
						else {
							// Bad
							this_private.trigger.call(event.data.loader, "error", {
								type: "parse",
								reason: "Could not parse the response"
							});
							this_private.status_change.call(event.data.loader, Loader.status.ERROR);
							this_private.complete.call(event.data.loader);
						}
					}
					else {
						if (event.status == 404) {
							// Mark thread as dead
							content.thread_delete(event.data.loader.board, event.data.loader.thread);
							this_private.status_change.call(event.data.loader, Loader.status.THREAD_DELETED);
							this_private.complete.call(event.data.loader);
						}
						else {
							// Error
							this_private.trigger.call(event.data.loader, "error", {
								type: "status",
								reason: "Expected ajax status 200, but received " + event.status
							});

							this_private.status_change.call(event.data.loader, Loader.status.ERROR);
							this_private.complete.call(event.data.loader);
						}
					}
				},
				ajax_on_progress: function (event) {
					// Progress
					event.data.loader.ajax_progress = (event.event.loaded / event.event.total);
					this_private.trigger.call(event.data.loader, "progress", {
						type: "ajax",
						ratio: event.data.loader.ajax_progress,
						loaded: event.event.loaded,
						total: event.event.total
					});
				},
				ajax_on_error: function (event) {
					// Error
					this_private.trigger.call(event.data.loader, "error", {
						type: "error",
						reason: "Ajax request error"
					});
					this_private.status_change.call(event.data.loader, Loader.status.ERROR);
					this_private.complete.call(event.data.loader);
				},
				ajax_on_abort: function (event) {
					// Error
					this_private.trigger.call(event.data.loader, "error", {
						type: "abort",
						reason: "Ajax request aborted"
					});
					this_private.status_change.call(event.data.loader, Loader.status.ERROR);
					this_private.complete.call(event.data.loader);
				},

				trigger: function (event, data) {
					// Trigger an event
					var e = this.events[event];
					for (var i = 0; i < e.length; ++i) {
						e[i][0].call(this, data, e[i][1], event);
					}
				},

				status_change: function (status) {
					var s = this.status;
					this.status = status;
					this_private.trigger.call(this, "status_change", {
						old_status: s,
						new_status: this.status
					});
				}

			};

			Loader.prototype = {

				constructor: Loader,

				run: function () {
					// Only run if not running
					if (this.status != Loader.status.NOT_RUNNING) return false;
					this_private.status_change.call(this, Loader.status.AJAX);

					// Run ajax
					if (this.source == null) {
						// New call
						xch.ajax({
							url: info.create_url.to_thread(this.board, this.thread),
							method: "GET",
							return_type: "text",
							on: {
								load: this_private.ajax_on_load,
								progress: this_private.ajax_on_progress,
								error: this_private.ajax_on_error,
								abort: this_private.ajax_on_abort
							},
							loader: this
						});
					}
					else {
						// Call from source event
						this.source.ajax_data.loader = this;

						// Trigger event
						this_private.ajax_on_load.call(this.source.ajax_data, this.source.event);
					}
					return true;
				},
				running: function () {
					return (this.status != Loader.status.NOT_RUNNING);
				},
				on: function (event, callback, callback_data) {
					if (event in this.events) {
						this.events[event].push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						// Check for callback
						var e = this.events[event];
						for (var i = 0; i < e.length; ++i) {
							if (e[i][0] == callback) {
								// Remove
								e.splice(i, 1);
								break;
							}
						}
					}
				}

			};

			Loader.status = {
				NOT_RUNNING: 0,
				AJAX: 1,
				ERROR: 2,
				PARSE_WAITING: 3,
				PARSING: 4,
				COMPLETE: 5
			};

			Loader.active_loaders = [];

			Loader.get_thread_from_ajax_load = function (board, thread, pre_event_hook, events, ajax_data, ajax_on_load_event, ajax_html) {
				// Check if it exists
				var loader, found = false;
				for (var i = Loader.active_loaders.length - 1; i >= 0; --i) {
					if (
						Loader.active_loaders[i].thread == thread &&
						Loader.active_loaders[i].board == board &&
						Loader.active_loaders[i].status == Loader.status.NOT_RUNNING && // must also not have been executed yet
						Loader.active_loaders[i].source == null // and have no source
					) {
						found = true;
						loader = Loader.active_loaders[i];
						break;
					}
				}

				// Create if it doesn't
				if (!found) {
					// Create
					loader = new Loader(board, thread);
					Loader.active_loaders.push(loader);
				}

				// Set source
				loader.source = {
					ajax_data: ajax_data,
					event: ajax_on_load_event,
					html: ajax_html
				};

				// Event check
				if (!pre_event_hook || pre_event_hook.call(loader, loader)) {

					// Add callbacks
					for (var event in events) {
						loader.on(event, events[event]);
					}

				}

				// Return
				return loader;
			};

			Loader.get_thread = function (board, thread, pre_event_hook, events) {
				// Check if it exists
				var loader, found = false;
				for (var i = Loader.active_loaders.length - 1; i >= 0; --i) {
					if (Loader.active_loaders[i].thread == thread && Loader.active_loaders[i].board == board) {
						found = true;
						loader = Loader.active_loaders[i];
						break;
					}
				}

				// Create if it doesn't
				if (!found) {
					// Create
					loader = new Loader(board, thread);
					Loader.active_loaders.push(loader);
				}

				// Event check
				if (!pre_event_hook || pre_event_hook.call(loader, loader)) {

					// Add callbacks
					for (var event in events) {
						loader.on(event, events[event]);
					}

				}

				// Return
				return loader;
			};

			return Loader;

		})(),


		// Messages
		Message: (function () {

			var Messenger = function () {
				this.container = null;

				this.default_message_life = 10;

				this.spacing = {
					from_top: 10,
					from_message: 8,
					from_minimized: 8
				};

				this.messages = [];

				this.paused = false;

				this.init();

				var data = { self: this };
				$(window)
				.on("blur", data, this.on_window_blur)
				.on("focus", data, this.on_window_focus);
			};

			Messenger.prototype = {

				constructor: Messenger,

				init: function () {
					this.container = style.e("div", "message_container");
					if (content && content.floating_container) {
						content.floating_container.append(this.container);
					}
				},

				on_window_blur: function (event) {
					event.data.self.pause_all();
				},
				on_window_focus: function (event) {
					event.data.self.resume_all();
				},

				pause_all: function () {
					this.paused = true;

					for (var i = 0; i < this.messages.length; ++i) {
						this_private.pause.call(this.messages[i]);
					}
				},
				resume_all: function () {
					this.paused = false;

					for (var i = 0; i < this.messages.length; ++i) {
						this.messages[i].set_lifetime(this.messages[i].life);
					}
				}

			};

			var messenger = null;



			var Message = function (params) {
				// Create a messenger
				if (messenger == null) messenger = new Messenger();

				// Events
				this.events = {
					close: {
						triggering: false,
						callbacks: [],
						removals: null
					}
				};

				// Data
				this.container = null;
				this.timer_display = null;
				this.life = 0;
				this.life_countdown = null;
				this.closeable = true;
				this.minimized = false;

				// Init
				this_private.init.call(this, params);
			};

			var this_private = {

				init: function (params) {
					var data = { self: this };

					this.container = style.e("div", "message");
					this.container.append(style.e("div", "message_background"));
					var inner = style.e("div", "message_inner");
					this.container.append(inner);

					// Style
					if ("type" in params) {
						if (params.type == "error") this.container.addClass("error");
						else if (params.type == "good") this.container.addClass("good");
						else if (params.type == "okay") this.container.addClass("okay");
						else if (params.type == "parse") this.container.addClass("parse");
						else if (params.type == "info") this.container.addClass("info");
						else this.container.addClass("plain");
					}
					else {
						this.container.addClass("plain");
					}

					// Title
					if ("title" in params) {
						inner.append(
							style.e("div", "message_title message_shadow")
							.text(params.title)
						);
					}

					// Content
					if ("html" in params) {
						inner.append(
							params.html.addClass("message_body")
						);
					}
					else if ("text" in params) {
						inner.append(
							style.e("div", "message_body")
							.text(params.text)
						);
					}

					// Events
					if ("on" in params) {
						for (var ev in params.on) {
							if (params.on[ev] instanceof Function) {
								this.on(ev, params.on[ev], undefined);
							}
							else {
								this.on(ev, params.on[ev].callback, params.on[ev].callback_data);
							}
						}
					}

					// Close button
					inner.append(
						style.e("div", "message_buttons message_hover_shadow")
						.append(
							style.e("span", "message_button close")
							.on("click", data, this_private.on_close_minimize_click)
						)
						.append(
							(this.timer_display = style.e("span", "message_timer"))
						)
					);

					// Timer
					this.set_closeable("closeable" in params ? params.closeable : true);
					this.set_lifetime("life" in params ? params.life || 0 : (this.closeable ? messenger.default_message_life : 0));
					if (messenger.paused) this_private.pause.call(this);

					// Events
					this.container.on("click", data, this_private.on_message_click);

					// Add
					messenger.container.append(this.container);

					this.id = messenger.messages.length;
					messenger.messages.push(this);

					// Position
					this_private.set_position.call(this);
				},

				set_position: function () {
					var min = this.minimized;

					var m_pre = null, i = this.id - 1;
					for (; i >= 0; --i) {
						m_pre = messenger.messages[i];
						if (m_pre.minimized == min) break;
					}
					if (i < 0) m_pre = null;

					if (m_pre == null) {
						// From top
						var area = content.header.get_screen_area();
						this.container.css("top", (area.top + messenger.spacing.from_top) + "px");
					}
					else {
						// From other message
						var m_pre_pos = m_pre.container.offset();
						var m_pre_height = m_pre.container.outerHeight();

						m_pre_pos.top -= $(window).scrollTop();

						this.container.css("top", (m_pre_pos.top + m_pre_height + (min ? messenger.spacing.from_minimized : messenger.spacing.from_message)) + "px");
					}
				},

				on_close_minimize_click: function (event) {
					if (event.which != 1) return true;

					if ($(this).hasClass("close")) {
						event.data.self.close();
					}
					else {
						event.data.self.set_minimized(!event.data.self.minimized);
					}

					return false;
				},
				on_message_click: function (event) {
					if (event.which != 1) return true;

					if (event.data.self.life_countdown !== null) {
						event.data.self.set_lifetime(0);
					}
				},

				pause: function () {
					if (this.life_countdown !== null) {
						clearInterval(this.life_countdown);
						this.life_countdown = null;
					}
				},

				trigger: function (event, data) {
					// Trigger an event
					var e = this.events[event];
					e.triggering = true;
					for (var i = 0, j = e.callbacks.length; i < j; ++i) {
						e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
					}
					e.triggering = false;
					if (e.removals != null) {
						for (var i = 0; i < e.removals.length; ++i) {
							this.off(event, e.removals[i]);
						}
						e.removals = null;
					}
				}

			};

			Message.prototype = {

				constructor: Message,

				close: function () {
					if (this.id < 0) return;

					// Events
					this_private.trigger.call(this, "close", {});
					this.events = {};

					// Remove events
					this.container.find(".message_button").off("click", this_private.on_close_minimize_click);
					this.container.off("click", this_private.on_message_click);

					// Stop timer
					if (this.life_countdown !== null) {
						clearInterval(this.life_countdown);
						this.life_countdown = null;
					}

					// Remove html
					this.container.remove();
					this.container = null;

					// Remove
					messenger.messages.splice(this.id, 1);

					// Update others
					for (var i = this.id; i < messenger.messages.length; ++i) {
						messenger.messages[i].id = i;
						this_private.set_position.call(messenger.messages[i]);
					}

					// Done
					this.id = -1;
				},

				is_open: function () {
					return (this.id >= 0);
				},

				set_minimized: function (minimized) {
					// Set to minimized
					this.minimized = minimized;
					if (minimized) {
						this.container.addClass("minimized");
					}
					else {
						this.container.removeClass("minimized");
					}

					// Reposition
					for (var i = this.id; i < messenger.messages.length; ++i) {
						this_private.set_position.call(messenger.messages[i]);
					}
				},
				set_lifetime: function (life) {
					this.life = (arguments.length > 0 ? life : messenger.default_message_life);

					if (this.life > 0) {
						var self = this;

						// Text
						this.timer_display.text(this.life).removeClass("hidden");

						// Start timer
						if (this.life_countdown !== null) {
							clearInterval(this.life_countdown);
						}
						this.life_countdown = setInterval(function () {
							self.life -= 1;
							if (self.life > 0) {
								self.timer_display.text(self.life);
							}
							else {
								self.timer_display.text("").removeClass("hidden");
							}
							if (self.life <= 0) {
								// Clear timer
								clearInterval(self.life_countdown);
								self.life_countdown = null;

								// Remove
								if (self.container.find(".close,.minimize").hasClass("close")) {
									self.close();
								}
								else {
									self.set_minimized(true);
								}
							}
						}, 1000);
					}
					else {
						this.timer_display.text("").addClass("hidden");

						if (this.life_countdown !== null) {
							clearInterval(this.life_countdown);
							this.life_countdown = null;
						}
					}
				},
				set_closeable: function (closeable) {
					this.closeable = closeable;
					if (closeable) {
						// Button
						this.container.find(".message_button").removeClass("minimize").addClass("close");
						this.timer_display.removeClass("hidden");
					}
					else {
						// Minimize
						this.container.find(".message_button").removeClass("close").addClass("minimize");
						this.timer_display.addClass("hidden");
					}
				},
				set_type: function (type) {
					style.set_class(this.container, "message");

					if (type == "error") this.container.addClass("error");
					else if (type == "good") this.container.addClass("good");
					else if (type == "okay") this.container.addClass("okay");
					else if (type == "parse") this.container.addClass("parse");
					else if (type == "info") this.container.addClass("info");
					else this.container.addClass("plain");
				},

				on: function (event, callback, callback_data) {
					// callback format: function (event_data, callback_data, event_name) with (this instanceof xch.Message)
					if (event in this.events) {
						// Add
						this.events[event].callbacks.push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							// Queue for removal
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							// Remove any callbacks
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
						}
					}
				}

			};

			return Message;

		})(),

		// Popups
		Popup: (function () {

			var current_popup = null;



			var Popup = function (params) {
				this.open = true;
				this.container = null;
				this.text_input = null;

				this.events = {
					before_close: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					close: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					moving: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					move: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					resizing: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					resize: {
						triggering: false,
						callbacks: [],
						removals: null
					}
				};

				this.position = {
					x: 0,
					y: 0,
					anchor: {
						x: 0,
						y: 0
					}
				};
				this.moving = {
					active: false,
					offset: {
						x: 0,
						y: 0
					}
				};

				this.size = {
					width: -1,
					height: -1
				};
				this.size_min = {
					width: 64,
					height: 64
				};
				this.resizing = null;


				this_private.init.call(this, params);
			};

			var this_private = {

				init: function (params) {
					var message_content, title_bar, close, body = null, bg;

					(this.container = style.e("div", "popup"))
					.append(
						(bg = style.e("div", "popup_background"))
					)
					.append(
						(message_content = style.e("div", "popup_content"))
						.append(
							(title_bar = style.e("div", "popup_title_bar popup_content_item"))
							.append(
								style.e("div", "popup_content_item_inner")
								.append(
									(close = style.e("span", "popup_close"))
								)
							)
						)
					);

					if ("no_close" in params) {
						close.addClass("hidden");
					}
					if ("title" in params) {
						if (typeof(params.title) == typeof("")) {
							title_bar.append(
								style.e("span", "popup_title")
								.text(params.title)
							);
						}
						else {
							title_bar.append(params.title.addClass("popup_title"));
						}
					}
					if ("text" in params || "html" in params || "textarea" in params) {
						message_content.append(
							style.e("div", "popup_body popup_content_item")
							.append(
								style.e("div", "popup_body_inner_1 popup_content_item_inner")
								.append(
									style.e("div", "popup_body_inner_2")
									.append(
										(body = style.e("div", "popup_body_inner_3"))
									)
								)
							)
						);

						if ("scroll" in params && !params.scroll) {
							body.addClass("no_scroll");
						}
						if ("text" in params) {
							body.append(
								style.e("div", "popup_description")
								.text(params.text)
							);
						}
						if ("html" in params) {
							body.append(
								style.e("div", "popup_description")
								.html(params.html)
							);
						}
						if ("textarea" in params) {
							if (params.textarea.single_line) {
								this.text_input = style.e("input", "popup_input").attr("type", "text");
							}
							else {
								this.text_input = style.e("textarea", "popup_input");
							}
							body.append(this.text_input);

							if ("value" in params.textarea) {
								this.text_input.val(params.textarea.value);
							}
							if ("height" in params.textarea) {
								this.text_input.css("height", params.textarea.height + "px");
							}
							if ("readonly" in params.textarea) {
								this.text_input.attr("readonly", "readonly");
							}
						}
					}
					if ("size" in params) {
						var bounds = content.header.get_screen_area();
						if ("width" in params.size) {
							var val = params.size.width;
							if (typeof(val) != typeof(0)) {
								var m = /([0-9\.]+)%/.exec(val);
								if (m) val = parseFloat(m[1]) / 100 * bounds.width;
							}
							this.container.css("width", val + "px");
						}
						if ("height" in params.size) {
							var val = params.size.height;
							if (typeof(val) != typeof(0)) {
								var m = /([0-9\.]+)%/.exec(val);
								if (m) val = parseFloat(m[1]) / 100 * bounds.height;
							}
							this.container.css("height", val + "px");
						}
					}
					if ("on" in params) {
						for (var ev in params.on) {
							if (params.on[ev] instanceof Function) {
								this.on(ev, params.on[ev], undefined);
							}
							else {
								this.on(ev, params.on[ev].callback, params.on[ev].callback_data);
							}
						}
					}
					if (params.packed) {
						message_content.addClass("packed");
					}
					if (params.resize) {
						// Resizers
						var resizers;
						bg.append(
							(resizers = style.e("div", "popup_resizers hidden"))
						);
						// Resizers
						for (var i = 0, r; i < 8; ++i) {
							// Create resizer
							resizers.append(
								(r = style.e("div", "popup_resizer"))
							)
							if (i < 3) r.addClass("top");
							else if (i >= 4 && i < 7) r.addClass("bottom");
							if (i == 0 || i >= 6) r.addClass("left");
							else if (i >= 2 && i < 5) r.addClass("right");
						}

						resizers.children(".popup_resizer")
						.on("mousedown", { self: this }, this_private.on_resizer_mousedown);

						this.resizing = {
							active: false,
							objects: [ this.container, this.container ],
							resizers: resizers,
							display_timer: null,
							x: 0,
							y: 0,
							base_pos: {
								x: 0,
								y: 0
							},
							base_size: {
								width: 0,
								height: 0
							},
							base_mouse: {
								x: 0,
								y: 0
							}
						};

						// objects
						if (params.resize instanceof Object) {
							if ("w_object" in params.resize) this.resizing.objects[0] = params.resize.w_object;
							if ("h_object" in params.resize) this.resizing.objects[1] = params.resize.h_object;
						}
					}
					if ("size_min" in params) {
						if ("width" in params.size_min) this.size_min.width = params.size_min.width || 0;
						if ("height" in params.size_min) this.size_min.height = params.size_min.height || 0;
					}
					if ("buttons" in params) {
						var buttons, button;
						message_content.append(
							style.e("div", "popup_buttons popup_content_item")
							.append(
								(buttons = style.e("div", "popup_content_item_inner"))
							)
						);
						for (var i = 0; i < params.buttons.length; ++i) {
							buttons.append(
								(button = style.e("button", "popup_button"))
							);
							if ("text" in params.buttons[i]) {
								button.text(params.buttons[i].text);
							}
							if ("html" in params.buttons[i]) {
								button.html(params.buttons[i].html);
							}
							if (params.buttons[i].text_only) {
								button.addClass("text_only");
							}
							if ("on" in params.buttons[i]) {
								for (var ev in params.buttons[i].on) {
									if (params.buttons[i].on[ev] instanceof Function) {
										button.on(ev, { popup: this, callback_data: undefined }, params.buttons[i].on[ev]);
									}
									else {
										button.on(ev, { popup: this, callback_data: params.buttons[i].on[ev].callback_data }, params.buttons[i].on[ev].callback);
									}
								}
							}
						}
					}

					// Add
					if (content && content.floating_container) {
						content.floating_container.append(this.container);
					}

					// Position
					var w = this.container.outerWidth();
					var h = this.container.outerHeight();
					var area = content.header.get_screen_area();
					this.set_position((area.left + (area.width - w) / 2), (area.top + (area.height - h) / 2), true, true);

					// Events
					var data = { self: this };
					close.on("mousedown", data, this_private.cancel_event)
					.on("click", data, this_private.on_close_click);
					title_bar.on("mousedown", data, this_private.on_grabber_mousedown);
					this.container
					.on("mousedown", data, this_private.on_container_mousedown)
					.on("mouseenter", data, this_private.on_container_mouseenter)
					.on("mouseleave", data, this_private.on_container_mouseleave);

					// Activate
					this.activate();
				},

				timeout_show_resizers: function () {
					if (settings.popup.resize_display_time <= 0) {
						this.resizing.resizers.removeClass("hidden");
					}
					else {
						var r = this.resizing;
						if (r.display_timer !== null) {
							clearTimeout(r.display_timer);
						}
						r.display_timer = setTimeout(function () {
							r.resizers.removeClass("hidden");
							r.display_timer = null;
						}, settings.popup.resize_display_time * 1000);
					}
				},

				cancel_event: function (event) {
					if (event.which != 1) return true;

					event.data.self.activate();

					return false;
				},

				on_close_click: function (event) {
					if (event.which != 1) return true;

					var returns = this_private.trigger.call(event.data.self, "before_close", {}, true);
					for (var i = 0; i < returns.length; ++i) {
						if (returns[i] === false) return false;
					}

					event.data.self.close();

					return false;
				},

				on_container_mousedown: function (event) {
					if (event.which != 1) return true;

					event.data.self.activate();
				},
				on_container_mouseenter: function (event) {
					if (event.data.self.resizing) {
						this_private.timeout_show_resizers.call(event.data.self);
					}
				},
				on_container_mouseleave: function (event) {
					var r = event.data.self.resizing;
					if (r) {
						r.resizers.addClass("hidden");
						if (r.display_timer !== null) {
							clearTimeout(r.display_timer);
						}
					}
				},

				on_grabber_mousedown: function (event) {
					if (event.which != 1) return true;

					// Stop
					var moving = event.data.self.moving;
					if (moving.active) {
						this_private.move_stop.call(event.data.self);
					}

					// Start
					$(window)
					.on("mousemove", event.data, this_private.on_window_mousemove_scroll)
					.on("scroll", event.data, this_private.on_window_mousemove_scroll)
					.on("mouseup", event.data, this_private.on_window_mouseup);

					moving.active = true;

					var o = event.data.self.container.offset();
					moving.offset.x = event.pageX - o.left;
					moving.offset.y = event.pageY - o.top;

					event.data.self.activate();

					// Done
					return false;
				},
				on_window_mousemove_scroll: function (event) {
					var win = $(window);
					var off = event.data.self.moving.offset;
					var x = event.pageX - win.scrollLeft() - off.x;
					var y = event.pageY - win.scrollTop() - off.y;

					event.data.self.set_position(x, y, false, true);
				},
				on_window_mouseup: function (event) {
					this_private.move_stop.call(event.data.self);
				},
				move_stop: function () {
					if (this.moving.active) {
						// Turn off events
						$(window)
						.off("mousemove", this_private.on_window_mousemove_scroll)
						.off("scroll", this_private.on_window_mousemove_scroll)
						.off("mouseup", this_private.on_window_mouseup);

						// Stop
						this.moving.active = false;
						this_private.trigger.call(this, "move", {});
					}
				},

				on_resizer_mousedown: function (event) {
					if (event.which != 1) return true;

					var self = event.data.self;
					var me = $(this);

					// Stop
					if (self.resizing.active) {
						this_private.resize_stop.call(self);
					}

					// Resize start
					self.resizing.active = true;

					if (me.hasClass("left")) {
						self.resizing.x = -1;
					}
					else if (me.hasClass("right")) {
						self.resizing.x = 1;
					}
					else {
						self.resizing.x = 0;
					}

					if (me.hasClass("top")) {
						self.resizing.y = -1;
					}
					else if (me.hasClass("bottom")) {
						self.resizing.y = 1;
					}
					else {
						self.resizing.y = 0;
					}

					self.resizing.base_pos.x = self.position.x;
					self.resizing.base_pos.y = self.position.y;

					self.resizing.base_size.width = self.size.width;
					self.resizing.base_size.height = self.size.height;

					if (self.resizing.base_size.width < 0) {
						self.resizing.base_size.width = self.resizing.objects[0].outerWidth();
					}
					if (self.resizing.base_size.height < 0) {
						self.resizing.base_size.height = self.resizing.objects[1].outerHeight();
					}

					self.resizing.base_mouse.x = event.pageX - $(window).scrollLeft();
					self.resizing.base_mouse.y = event.pageY - $(window).scrollTop();

					// Activate
					self.activate();

					// Start
					$(window)
					.on("mousemove", event.data, this_private.on_resize_window_mousemove)
					.on("mouseup", event.data, this_private.on_resize_window_mouseup);

					return false;
				},
				on_resize_window_mousemove: function (event) {
					this_private.resize.call(event.data.self, { x:event.pageX, y:event.pageY });
				},
				on_resize_window_mouseup: function (event) {
					this_private.resize_stop.call(event.data.self);
				},
				resize_stop: function () {
					$(window)
					.off("mousemove", this_private.on_resize_window_mousemove)
					.off("mouseup", this_private.on_resize_window_mouseup);

					this.resizing.active = false;
					this_private.trigger.call(this, "resize", {});
				},

				resize: function (mouse) {
					// Normalize
					var win = $(window);
					mouse.x -= win.scrollLeft();
					mouse.y -= win.scrollTop();
					var pos = this.container.offset();
					var conts = this.resizing.objects;
					var diff, pos_update = false;
					var xy = ["x","y"];
					var wh = ["width","height"];

					// Resize
					for (var i = 0; i < 2; ++i) {
						if (this.resizing[xy[i]] != 0 && this.resizing[xy[i]] != this.position.anchor[xy[i]]) {
							// Difference
							diff = (mouse[xy[i]] - this.resizing.base_mouse[xy[i]]) * this.resizing[xy[i]];
							// Bound
							if (this.resizing.base_size[wh[i]] + diff < this.size_min[wh[i]]) {
								diff = this.size_min[wh[i]] - this.resizing.base_size[wh[i]];
							}

							// Set values
							this.size[wh[i]] = this.resizing.base_size[wh[i]] + diff;
							if (this.resizing[xy[i]] == -1) {
								this.position[xy[i]] = this.resizing.base_pos[xy[i]] - diff;
								pos_update = true;
							}

							// Apply
							conts[i].css(wh[i], this.size[wh[i]] + "px");
						}
					}

					// Update
					if (pos_update) {
						this.set_position(this.position.x, this.position.y, true, true);
					}
					this_private.trigger.call(this, "resizing", {
						position: pos_update
					});
				},

				trigger: function (event, data, returns) {
					// Trigger an event
					var r_list = returns ? [] : null;
					var e = this.events[event], r;
					e.triggering = true;
					for (var i = 0, j = e.callbacks.length; i < j; ++i) {
						r = e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
						if (returns) r_list.push(r);
					}
					e.triggering = false;
					if (e.removals != null) {
						for (var i = 0; i < e.removals.length; ++i) {
							this.off(event, e.removals[i]);
						}
						e.removals = null;
					}
					return returns ? r_list : undefined;
				}

			};

			Popup.prototype = {

				close: function () {
					if (!this.open) return;

					if (this.moving.active) {
						this_private.move_stop.call(this);
					}
					if (this.resizing && this.resizing.active) {
						this_private.resize_stop.call(this);
					}

					if (current_popup == this) {
						this.container.removeClass("active");
						current_popup = null;
					}

					this_private.trigger.call(this, "close", {});
					this.events = {};

					this.open = false;

					this.container.find(".popup_title_bar").off("mousedown", this_private.on_grabber_mousedown);

					this.container.remove();
					this.container = null;
				},

				hide: function () {
					if (!this.open) return;

					this.container.addClass("hidden");
				},
				show: function () {
					if (!this.open) return;

					this.container.removeClass("hidden");
				},

				is_visible: function () {
					return (this.open && !this.container.hasClass("hidden"));
				},

				activate: function () {
					if (current_popup != this) {
						if (current_popup != null) {
							current_popup.container.removeClass("active");
						}
						this.container.addClass("active");
						current_popup = this;
					}
				},

				set_position: function (x, y, no_anchor_updates, no_trigger) {
					this.position.x = x;
					this.position.y = y;

					var bounds = content.header.get_screen_area();
					var size = {
						w: this.container.outerWidth(),
						h: this.container.outerHeight()
					};

					// Bounds updating
					if (!no_anchor_updates || this.position.anchor.x == 0) {
						this.position.anchor.x = 0;
						if (x + size.w > bounds.left + bounds.width - settings.popup.anchor_border_size) {
							this.position.anchor.x = 1;
							x = bounds.left + bounds.width - size.w;
						}
						if (x < bounds.left + settings.popup.anchor_border_size) {
							this.position.anchor.x = -1;
						}
					}

					if (!no_anchor_updates || this.position.anchor.y == 0) {
						this.position.anchor.y = 0;
						if (y + size.h > bounds.top + bounds.height - settings.popup.anchor_border_size) {
							this.position.anchor.y = 1;
							y = bounds.top + bounds.height - size.h;
						}
						if (y < bounds.top + settings.popup.anchor_border_size) {
							this.position.anchor.y = -1;
						}
					}

					// Position
					if (this.position.anchor.x == 0) {
						this.container.css("left", x + "px").css("right", "");
					}
					else if (this.position.anchor.x == -1) {
						this.container.css("left", bounds.left + "px").css("right", "");
					}
					else {
						this.container.css("right", ($(window).width() - (bounds.left + bounds.width)) + "px").css("left", "");
					}

					if (this.position.anchor.y == 0) {
						this.container.css("top", y + "px").css("bottom", "");
					}
					else if (this.position.anchor.y == -1) {
						this.container.css("top", bounds.top + "px").css("bottom", "");
					}
					else {
						this.container.css("bottom", ($(window).height() - (bounds.top + bounds.height)) + "px").css("top", "");
					}

					// Event
					if (!no_trigger) {
						this_private.trigger.call(this, "move", {});
					}
				},
				set_size: function (width, height, no_trigger) {
					this.size.width = width;
					this.size.height = height;

					// Resize
					if (this.resizing) {
						this.resizing.objects[0].css("width", this.size.width + "px");
						this.resizing.objects[1].css("height", this.size.height + "px");

						this.set_position(this.position.x, this.position.y, true, no_trigger);
					}

					// Event
					if (!no_trigger) {
						this_private.trigger.call(this, "resize", {});
					}
				},

				is_open: function () {
					return (this.container != null);
				},

				on: function (event, callback, callback_data) {
					// callback format: function (event_data, callback_data, event_name) with (this instanceof xch.Popup)
					if (event in this.events) {
						// Add
						this.events[event].callbacks.push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							// Queue for removal
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							// Remove any callbacks
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
						}
					}
				}

			};

			return Popup;

		})(),

		// Menu
		Menu: (function () {

			var Menu = function (parent, params, source) {
				// Parent
				this.parent = parent;
				this.source = source;
				this.open = !source;

				// Settings
				this.keep_on_screen = false;
				this.close_on_mouseleave = false;
				this.fit_width_min = false;
				this.fit_height_min = false;
				this.max_idle_time = 0;
				this.fixed_z = null;
				this.use_fixed_z = true;
				this.open_direction = 1;
				this.ready = false;
				this.anchor = null;
				this.menu_class = null;

				// Options
				this.options = [];
				this.option_expanded = null;

				// Container
				this.container = null;
				this.options_container = null;

				// Closing
				this.close_timer = null;

				// Custom events
				this.events = {
					open: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					close: {
						triggering: false,
						callbacks: [],
						removals: null
					}
				};

				// Timeout init
				this.can_click_to_close = false;
				this.max_idle_timer = null;

				// Events
				this.registered_events = [];

				// Init
				this_private.init.call(this, params);
			};

			var this_private = {

				init: function (params) {
					var self = this;

					// Settings
					this.keep_on_screen = params.keep_on_screen || false;
					this.close_on_mouseleave = params.close_on_mouseleave || false;
					this.fit_width_min = params.fit_width_min || false;
					this.fit_height_min = params.fit_height_min || false;
					this.max_idle_time = ("max_idle_time" in params ? params.max_idle_time : 0);
					this.fixed_z = ("fixed_z" in params ? params.fixed_z : null);
					this.use_fixed_z = ("fixed_z" in params);
					if (params.open == "up") this.open_direction = 0;
					else if (params.open == "down") this.open_direction = 1;
					else if (params.open == "left") this.open_direction = 2;
					else this.open_direction = 3; // right/default

					// Anchors
					if ("anchor" in params) {
						this.anchor = {};
						if (params.anchor.x) {
							this.anchor.x = [ params.anchor.x.container , params.anchor.x.container_side == "right" , params.anchor.x.menu_side == "right" ];
						}
						if (params.anchor.y) {
							this.anchor.y = [ params.anchor.y.container , params.anchor.y.container_side == "bottom" , params.anchor.y.menu_side == "bottom" ];
						}
					}

					// Container
					(this.container = style.e("div", "menu"))
					.append(
						style.e("div", "menu_background")
					)
					.append(
						(this.options_container = style.e("div", "menu_options"))
					);
					if (!this.open) this.container.addClass("hidden");
					if ("menu_class" in params) {
						this.menu_class = params.menu_class;
						this.container.addClass(params.menu_class);
					}
					if ("menu_class_inherit" in params) {
						this.menu_class = params.menu_class_inherit;
					}

					// Add options
					if ("options" in params) {
						for (var i = 0; i < params.options.length; ++i) {
							this.add_option(params.options[i]);
						}
					}

					// Add
					content.floating_container.append(this.container);

					// Update
					if (this.open) {
						this.update();
					}

					// Ready
					this.ready = true;

					// Events
					if ("on" in params) {
						for (var ev in params.on) {
							if (params.on[ev] instanceof Function) {
								this.on(ev, params.on[ev], undefined);
							}
							else {
								this.on(ev, params.on[ev].callback, params.on[ev].callback_data);
							}
						}
					}

					// Timeout init
					this_private.init_timeouts.call(this);

					// Events
					var ev_data = { self: this };
					this_private.register_event.call(this, this.container, "mouseenter", this_private.on_container_mouseenter, ev_data);
					this_private.register_event.call(this, this.container, "mouseleave", this_private.on_container_mouseleave, ev_data);
					this_private.register_event.call(this, this.container, "click", this_private.on_container_click, ev_data);
					this_private.register_event.call(this, $(document), "click", this_private.on_document_click, ev_data);
					this_private.register_event.call(this, $(window), "scroll", this_private.on_window_scroll, ev_data);
					this_private.register_event.call(this, $(window), "resize", this_private.on_window_resize, ev_data);
				},

				init_timeouts: function () {
					var self = this;

					setTimeout(function () {
						self.can_click_to_close = true;
					}, 50);
					if (this.max_idle_time > 0) {
						this.max_idle_timer = setTimeout(function () {
							self.max_idle_timer = null;
							self.close();
						}, this.max_idle_time * 1000);
					}
				},

				on_container_mouseenter: function (event) {
					if (event.data.self.max_idle_timer !== null) {
						clearTimeout(event.data.self.max_idle_timer);
						event.data.self.max_idle_timer = null;
					}
				},
				on_container_mouseleave: function (event) {
					if (event.data.self.close_on_mouseleave) event.data.self.close();
				},
				on_container_click: function (event) {
					if (event.which != 1) return true;

					if (event.data.self.close_timer !== null) {
						clearTimeout(event.data.self.close_timer);
						event.data.self.close_timer = null;
					}

					return false;
				},
				on_document_click: function (event) {
					if (event.which != 1 || !event.data.self.can_click_to_close) return true;

					if (event.data.self.close_timer === null) {
						this_private.activate_close_timer.call(event.data.self);
					}
				},
				on_window_scroll: function (event) {
					event.data.self.update();
				},
				on_window_resize: function (event) {
					event.data.self.update();
				},

				activate_close_timer: function () {
					var self = this;
					this.close_timer = setTimeout(function () {
						self.close_timer = null;
						self.close();
					}, 50);
				},

				register_event: function (object, event, callback, data) {
					this.registered_events.push([object,event,callback]);
					object.on(event, null, data || null, callback);
				},
				unregister_events: function () {
					for (var i = 0; i < this.registered_events.length; ++i) {
						this.registered_events[i][0].off(this.registered_events[i][1], null, this.registered_events[i][2]);
					}
					this.registered_events = [];
				},
				destroy: function () {
					// Remove events
					this_private.unregister_events.call(this);
					// Remove container
					this.container.remove();
					// Destroy children
					for (var i = 0; i < this.options.length; ++i) {
						this.options[i].destroy();
					}
				},

				trigger: function (event, data) {
					// Trigger an event
					var e = this.events[event];
					e.triggering = true;
					for (var i = 0, j = e.callbacks.length; i < j; ++i) {
						e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
					}
					e.triggering = false;
					if (e.removals != null) {
						for (var i = 0; i < e.removals.length; ++i) {
							this.off(event, e.removals[i]);
						}
						e.removals = null;
					}
				},

				on_option_click: function (event) {
					if (event.which != 1) return true;

					if (event.data.option.sub_menu != null && event.data.option.expand_on_click) {
						event.data.option.expand();
					}
				},
				on_option_mouseenter: function (event) {
					if (event.data.option.expand_on_hover) {
						if (event.data.option.sub_menu == null) {
							// Hide
							if (event.data.option.menu.option_expanded != null) {
								event.data.option.menu.option_expanded.unexpand();
							}
						}
						else {
							// Expand
							event.data.option.expand();
						}
					}
				}

			};

			Menu.prototype = {

				constructor: Menu,

				add_option: function (params) {
					// Create
					var opt = new Option(params, this);

					// Needs to update
					if (this.ready) {
						this.update();
					}

					// Done
					return opt;
				},
				update: function (full) {
					// Skip
					if (this.container.hasClass("hidden")) return;

					var self = this;

					// Fixed?
					this.fixed = false;
					var p_parents = this.parent.parents();
					var z_index = 0;
					for (var i = 0; i < p_parents.length; ++i) {
						var me = $(p_parents[i]);
						if (me.css("position").indexOf("fixed") >= 0) this.fixed = true;

						var zi = me.css("z-index");
						if (zi != "auto") z_index = zi;
					}
					if (this.fixed) this.container.addClass("fixed");
					else this.container.removeClass("fixed");
					if (this.use_fixed_z) {
						if (this.fixed_z !== null) {
							this.container.css("z-index", this.fixed_z);
						}
					}
					else {
						this.container.css("z-index", parseInt(z_index) + 10);
					}

					// Update size, etc.
					if (this.fit_width_min) {
						this.container.css("min-width", this.parent.width().toFixed(2) + "px");
					}
					if (this.fit_height_min) {
						this.container.css("min-height", this.parent.height().toFixed(2) + "px");
					}

					// Position vars
					var win = $(window);
					var win_scroll = {
						left: win.scrollLeft(),
						top: win.scrollTop()
					};
					var screen_area = content.header.get_screen_area();
					var p_pos = this.parent.offset();
					var p_size = [ this.parent.outerWidth() , this.parent.outerHeight() ];
					var c_size = [ this.container.outerWidth() , this.container.outerHeight() ];

					if (this.fixed) {
						p_pos.left -= win_scroll.left;
						p_pos.top -= win_scroll.top;
					}
					else {
						screen_area.left += win_scroll.left;
						screen_area.top += win_scroll.top;
					}
					p_pos = [ p_pos.left , p_pos.top ];
					var screen_pos = [ screen_area.left , screen_area.top ];
					var screen_size = [ screen_area.width , screen_area.height ];

					// Open direction
					var dirs = [ true , true ], ind;
					switch (this.open_direction) {
						case 0: // up
						{
							dirs[0] = false;
							ind = 1;
						}
						break;
						case 1: // down
						{
							dirs[0] = true;
							ind = 1;
						}
						break;
						case 2: // left
						{
							dirs[0] = false;
							ind = 0;
						}
						break;
						case 3: // right
						{
							dirs[0] = true;
							ind = 0;
						}
						break;
					}
					var l2r = [ "left" , "right" ];
					var u2d = [ "top" , "bottom" ];
					var x2y = [ "x" , "y" ];
					var l2t = [ "left" , "top" ];
					var pos = [ 0 , 0 ];

					// Main axis
					for (var d = 0, dir, anc; d < 2; ++d) {
						dir = dirs[d];
						if (this.anchor && (anc = this.anchor[x2y[ind]])) {
							pos[ind] = anc[0].offset()[l2t[ind]] + (anc[1] ? (ind == 0 ? anc[0].outerWidth() : anc[0].outerHeight()) : 0) - (anc[2] ? c_size[ind] : 0);
						}
						else {
							for (var i = 0; i < 2; ++i) {
								pos[ind] = p_pos[ind] + (dir ? (d == 0 ? p_size[ind] : 0) : -c_size[ind] + (d == 0 ? 0 : p_size[ind]));
								if ((pos[ind] + (dir ? c_size[ind] : -c_size[ind]) > screen_pos[ind] + (dir ? screen_size[ind] : 0)) == dir) {
									dir = !dir;
								}
								else break;
							}
						}
						// Bound
						if (this.keep_on_screen) {
							if (pos[ind] + c_size[ind] > screen_pos[ind] + screen_size[ind]) pos[ind] = screen_pos[ind] + screen_size[ind] - c_size[ind];
							if (pos[ind] < screen_pos[ind]) pos[ind] = screen_pos[ind];
						}
						// Next axis
						ind = 1 - ind;
					}
					this.container.css("left", pos[0] + "px");
					this.container.css("top", pos[1] + "px");

					// Update children menus
					for (var i = 0; i < this.options.length; ++i) {
						if (this.options[i].sub_menu != null) {
							this.options[i].sub_menu.update();
						}
					}
				},
				show: function () {
					if (this.container.hasClass("hidden")) {
						this.container.removeClass("hidden");
						this.update();
						// Event
						this_private.trigger.call(this, "open", {
							menu: this
						});
					}
				},
				hide: function () {
					// Unexpand anything open
					if (this.option_expanded != null) {
						this.option_expanded.unexpand();
					}

					// Hide
					this.container.addClass("hidden");

					// Event
					this_private.trigger.call(this, "close", {
						menu: this
					});
				},
				close: function () {
					if (this.source) {
						// Unexpand anything open
						if (this.option_expanded != null) {
							this.option_expanded.unexpand();
						}

						// Hide
						this.container.addClass("hidden");

						// Event
						this_private.trigger.call(this, "close", {
							menu: this
						});

						// Close source
						this.source.menu.close();
					}
					else {
						// Unexpand anything open
						if (this.option_expanded != null) {
							this.option_expanded.unexpand();
						}

						// Destroy
						this_private.destroy.call(this);

						// Event
						this_private.trigger.call(this, "close", {
							menu: this
						});
					}
				},

				on: function (event, callback, callback_data) {
					// callback format: function (event_data, callback_data, event_name) with (this instanceof xch.Message)
					if (event in this.events) {
						// Add
						this.events[event].callbacks.push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							// Queue for removal
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							// Remove any callbacks
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
						}
					}
				}

			};

			var Option = (function () {

				var Option = function (params, parent) {
					this.menu = parent;

					// Sub menus
					this.sub_menu = null;
					this.expand_on_click = ("expand_on_click" in params ? params.expand_on_click : true);
					this.expand_on_hover = ("expand_on_hover" in params ? params.expand_on_hover : true);
					this.order = params.order || 0;

					// Container
					if ("text" in params) {
						this.container = style.e("div", "menu_option");
						this.container.text(params.text);
					}
					else if ("html" in params) {
						this.container = style.add_class(params.html.addClass("menu_option"));
					}
					else {
						this.container = style.e("div", "menu_option");
						this.container.text("null");
					}

					// Other stuff
					var conts = this.container.contents();
					conts.wrap(style.e("div", "menu_option_content"));
					this.container.prepend(
						style.e("div", "menu_option_background")
					);

					// Events
					this.registered_events = [];
					if ("on" in params) {
						for (var ev in params.on) {
							if (params.on[ev] instanceof Function) {
								this.container.on(ev, { option: this, callback_data: undefined }, params.on[ev]);
							}
							else {
								this.container.on(ev, { option: this, callback_data: params.on[ev].callback_data }, params.on[ev].callback);
							}
						}
					}
					var data = { option: this };
					this.container
					.on("click", data, this_private.on_option_click)
					.on("mouseenter", data, this_private.on_option_mouseenter);

					// Add
					var i;
					for (i = this.menu.options.length - 1; i >= 0; --i) {
						if (this.order >= this.menu.options[i].order) {
							this.menu.options[i].container.after(this.container);
							this.menu.options.splice(i + 1, 0, this);
							break;
						}
					}
					if (i < 0) {
						this.menu.options_container.prepend(this.container);
						this.menu.options.splice(0, 0, this);
					}

					// Sub menu
					if ("sub_menu" in params) {
						this.sub_menu = new Menu(this.container, params.sub_menu, this);

						if (!("hidden_expand" in params)) {
							this.container.addClass("expandable");
							this.menu.container.children(".menu_options").addClass("expandable");
						}
					}
					else if ("options" in params) {
						var params_new = {
							close_on_mouseleave: false,
							keep_on_screen: this.menu.keep_on_screen,
							fixed_z: null,
							open: "right",
							options: params.options
						};
						if (this.menu.menu_class !== null) {
							params_new.menu_class = this.menu.menu_class;
						}
						this.sub_menu = new Menu(this.container, params_new, this);

						if (!("hidden_expand" in params)) {
							this.container.addClass("expandable");
							this.menu.container.children(".menu_options").addClass("expandable");
						}
					}
				};

				Option.prototype = {

					constructor: Option,

					expand: function () {
						if (this.menu.option_expanded != null) {
							this.menu.option_expanded.unexpand();
						}
						this.sub_menu.show();
						this.menu.option_expanded = this;
					},
					unexpand: function () {
						this.menu.option_expanded = null;
						this.sub_menu.hide();
					},
					destroy: function () {
						this_private.unregister_events.call(this);

						if (this.sub_menu != null) {
							this_private.destroy.call(this.sub_menu);
						}
					}

				};

				return Option;

			})();

			return Menu;

		})(),


		// Meta information
		Meta: (function () {

			var Meta = function () {
				this.page_okay = false;
				this.page_code = 200;

				var m = /\/(\w+)\/(?:($)|(?:(index)\.html|([0-9]+).html)|res\/([0-9]+)\.html)/.exec(document.location.pathname);
				if (m) {
					this.page_okay = true;
					var board = m[1];
					var page_number = (m[2] === undefined ? (m[3] === undefined ? parseInt(m[4]) : 1) : 1);
					var thread_number = (m[5] === undefined ? null : parseInt(m[5]));
				}
			};

			Meta.prototype = {

				constructor: Meta,

				validate: function (html) {
					if (html.find("title:nth-of-type(1)").text() == "404 Not Found") {
						this.page_code = 404;
						return false;
					}

					if (html.find('form[name="postcontrols"]').length <= 0) {
						// No content?
						return false;
					}

					return true;
				}

			};

			return Meta;

		})(),

		// Page information
		Information: (function () {

			var Information = function (meta, html) {
				this.meta = meta;

				this.board = "";
				this.index = false;
				this.page_index = 0;

				this.images = {
					deleted: {
						url: "http://boards.38chan.net/static/deleted.png",
						size: { width: 140, height: 50 }
					}
				};

				this.favicons = {
					alive: {
						standard: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAXElEQVQ4y81SQQ7AIAyC/v/P7OpMQWOWOC41rWAtBW6Dw1kmj3SHTcGJqHmBroNZpCUDQAWyhYaQOmD4f5rTDyAzkzZvreLkguOV83nPiLfAEQofe6OwWDpZuiUe2UAbAR05NuwAAAAASUVORK5CYII=",
						new_post: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAzElEQVQ4y6WSsRHDMAhFn3OZwY23Se9KA6j1JauZij5DeAwvQYqcFBkjN9EdJ4rPF/AEf56hJKpqJZ/neYjEllLVDCI/jaraNE01WrO2+Ak1qlkpVtUa3qQUW0o1QpOcs+WcDyaHYjBzHdxU1ZZlYRxHtm0DYF3X8wJEvhfwAF4inDro7cHP33YQUohItAROFLzQi8sDEZ0uqtakHbFr4lGV3GMOTa5QXSEGuHtUAqTOiAVzew4GArwvivd9D01OY/SW2N2BZ93DGP2XD/B7EnK1NF7SAAAAAElFTkSuQmCC",
						new_post_to_me: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABEklEQVQ4y6WSsXHEIBBFHzOuQQkNyImKuNiKtgBSxq04vvxUABG5iyCxG1ByTawDAwNIcuKdYYR29D/sf4J/limbGKOW/bqupvTeHg9MCAZAReo3pVfF1tq6YoyaDXUGVRFVEX2HuqpZERdReQe01NyIWrODiXNOnXP19NZARVRBdbiBiTGq955pmgBYlgWAbdtQ/T3g1Ri+8rghPz+Buwg1LO99l+6+753BbUj/LoIJwZxSyCT6G4j0+DIFM3IdUDEDHzEyIj6tFlVJuyVTaP0pbjHNoECH+dSkE2dUc4MR6BC3Ji+dUwgEQC5GTCkdep1ByHwBbjn9b8BaS0qJ5/N5anIY4yrEywxGhC3K8T8ZxT+A1yOTNT7ixAAAAABJRU5ErkJggg=="
					},
					dead: {
						standard: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVQ4y82RMQ4AIAgDgf//GScTQlpBFu2ioZUgJ/JaGu5O6nLK7KCD17kJzGgVqDxDgUoeDrYD+IXC/0gOxqV1iopRyIiNYewQyA1GMrlgo/0J6RKZN9cCTJQgAMuv+3kAAAAASUVORK5CYII=",
						new_post: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAiUlEQVQ4y6VSQQ7AIAjDxR/yi71tfMGvsYsQRygcZkJCaKlCJfp5hiXKrF4UGRkZcpRZbyKPk9hyDFBmjyjScrxgERo833ja/OyIpChuvFQE7aHCUxcyJxA+YyFzoBKGVqER4AVw8yDHzY0LUeT6KImQFPPL5pxnRsIqmle13fNp1VduF4msRNgLwaUKCulRKBwAAAAASUVORK5CYII=",
						new_post_to_me: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAyklEQVQ4y6VSwQ3DMAg8qgyTrJEVbot0nmxRVsgaGYc+aixMaPooEhKG42xzAH+aeGCk9aSqxFw+x9zkhacqMtnScn7OGFEVGGkbYEZ23wCbAXObgRLTX9QT7uSFwMheL5tfzR10IWjuOCeRagYAcAA47XPJIoI1TX8nIapSquADjAQnOcrXVJCsayRyFWKzN172IH9lTzfGfCYZlUhSZgkHBcrmHypkksfApAq92XttmGhTBhwtXtv0PY610uLT8tpWta8k1U7c1d5z5w0XUkvMIwAAAABJRU5ErkJggg=="
					}
				};

				// Implicit parsing
				this_private.parse.call(this, html);
			};

			var this_private = {

				parse: function (html) {
					// Board
					var p = html.find('form[name="postcontrols"]');
					if (p.length > 0) {
						this.board = p.find('input[name="board"]').val() || "";
					}

					// Page type
					p = html.find("div.pages");
					if (p.length > 0) {
						// On an index page
						this.index = true;
						p = $(p[0]);
						p = p.find("a.selected");
						if (p.length > 0) {
							this.page_index = parseInt(p.html()) || 0;
						}
					}
					else {
						// Not on the index
						this.index = false;
					}
				}

			};

			Information.prototype = {

				constructor: Information,

				urls_match: function (url1, url2) {
					return (url1 == url2);
				},
				create_url: {
					to_board: function (board, page) {
						return "/" + board + "/" + (arguments.length >= 2 && page > 1 ? (page + ".html") : "");
					},
					to_thread: function (board, thread_id) {
						return "/" + board + "/res/" + thread_id + ".html";
					},
					to_post: function (board, thread_id, post_id) {
						return "/" + board + "/res/" + thread_id + ".html#" + post_id;
					},
					to_post_submit_target: function (board, thread_id) {
						return "/post.php";
					},
					to_report_submit_target: function (board, thread_id) {
						return "/post.php";
					},
					to_delete_submit_target: function (board, thread_id) {
						return "/post.php";
					}
				},

				is_default_name: function (name, board) {
					return (name == "Anonymous");
				}

			};

			return Information;

		})(),

		// Post parsing queue
		PostQueue: (function () {

			var PostQueue = function (max, timeout) {
				this.post_queue = [];
				this.count = 0;
				this.timer = null;
				this.max = max;
				this.timeout = timeout * 1000;
			};

			var this_private = {

				test_queue: function () {
					if (this.timer === null) {
						if (this.count < this.max || this.max <= 0) {
							// Parse now
							var post_data = this.post_queue.shift();
							post_data[0].parse_immediate(post_data[1]);
							// Increase count
							++this.count;
							// Next
							return true;
						}
						else {
							// Timer
							var self = this;
							this.timer = setTimeout(function () {
								// Reset
								self.timer = null;
								self.count = 0;
								// Parse more if they exist
								while (self.post_queue.length > 0 && this_private.test_queue.call(self));
							}, this.timeout);
						}
					}
					// Did not parse any
					return false;
				}

			};

			PostQueue.prototype = {

				constructor: PostQueue,

				queue: function (post, data) {
					this.post_queue.push([post, data]);
					this_private.test_queue.call(this);
				}

			};

			return PostQueue;

		})(),

		// Page content
		Content: (function () {

			var Content = function (main_context) {
				this.threads = [];
				this.board_map = {};
				this.incomplete_references = [];
				this.dead_references = [];

				this.main_context = main_context;

				this.container = null;

				this.floating_container = null;

				this.updater = null;

				this.header = null;
				this.qr = null;

				this.events = {
					thread_new: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					thread_ready: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					thread_load: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					thread_delete: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					thread_update: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					post_new: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					post_load: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					post_delete: {
						triggering: false,
						callbacks: [],
						removals: null
					},
					complete: {
						triggering: false,
						callbacks: [],
						removals: null
					}
				};

				this.title_format = {
					index: "/%board/ - %title",
					thread: "/%board/ - %op"
				};

				this.my_posts = {};

				this.favicon_node = null;

				// Delete/reporting
				this.delete_report_popup = null;
				this.post_marking_popup = null;

				// Sourcing nodes
				this.sourcing = [];

				// Load
				this_private.load_all.call(this);

				// Sourcing update
				this_private.update_sourcing.call(this);

				// Highlight waiting
				this.post_highlight_id_target = -1;

				// Event listening
				location.on("change", this_private.on_location_change, this);

				// Title update with OP
				if (!info.index) {
					this.on("post_load", this_private.on_post_load_title_update, this);
				}

				communication.cross_tab_on("my_posts_change", this_private.on_cross_my_posts_change, this);

				// Initial location test
				this_private.on_location_change.call(location, {
					location: location,
					self_init: true
				}, this);
			};

			var this_private = {

				load_all: function () {
					this_private.load_my_posts.call(this, false);
				},
				load_my_posts: function (apply) {
					var my_posts = xch.site_get_value_raw("my_posts", null);
					if (my_posts !== null && my_posts.length == 0) my_posts = null;

					if (my_posts !== null) {
						var sep = "/";
						var my_posts_split = my_posts.split(sep);
						for (var i = 0; i < my_posts_split.length; ++i) {
							// Load by key
							this_private.load_my_post_by_id.call(this, my_posts_split[i], apply);
						}
					}
				},
				load_my_post_by_id: function (key, apply) {
					var val = xch.site_get_value("my_posts_" + key, null);
					if (val !== null) {
						// Set
						this.my_posts[key] = val;

						// Apply?
						if (apply) {
							// Find post
							var key_split = this_private.unformat_my_post_key.call(this, event.key);
							var board = key_split[0];
							var thread_id = key_split[1];
							var post_id = key_split[2];

							if (
								board in this.board_map &&
								thread_id in this.board_map[board].thread_map &&
								post_id in this.board_map[board].thread_map[thread_id].post_map
							) {
								// Post found
								var post = this.board_map[board].thread_map[thread_id].post_map[post_id];

								// Update references
								post.update_all_reply_references();
								post.update_all_backlink_references();
							}
						}
					}
				},
				save_my_post: function (new_post_key, add) {
					var my_posts = xch.site_get_value_raw("my_posts", null);
					if (my_posts !== null && my_posts.length == 0) my_posts = null;

					// Check for key
					var sep = "/";
					var i = 0, my_posts_split = null;
					if (my_posts !== null) {
						my_posts_split = my_posts.split(sep);
						for (; i < my_posts_split.length; ++i) {
							if (my_posts_split[i] == new_post_key) break;
						}
					}

					if (add) {
						if (my_posts_split == null || i == my_posts_split.length) {
							// Add
							if (my_posts === null) my_posts = "";
							else if (my_posts.length > 0) my_posts += sep;
							my_posts += new_post_key;

							// Save
							xch.site_set_value_raw("my_posts", my_posts);
						}

						// Save data
						xch.site_set_value("my_posts_" + new_post_key, this.my_posts[new_post_key]);
					}
					else {
						// Remove
						if (my_posts_split != null && i < my_posts_split.length) {
							my_posts_split.splice(i, 1);
							if (my_posts_split.length > 0) {
								// Set
								xch.site_set_value_raw("my_posts", my_posts_split.join(sep));
							}
							else {
								// Empty: remove
								xch.site_delete_value("my_posts");
							}
						}

						// Delte data
						xch.site_delete_value("my_posts_" + new_post_key);
					}

					// Trigger event
					communication.cross_tab_trigger("my_posts_change", {
						key: new_post_key,
						add: add
					});
				},

				on_cross_my_posts_change: function (event, self) {
					if (event.add) {
						// Load
						this_private.load_my_post_by_id.call(self, event.key, true);
					}
					else {
						// Unmark without save
						var key_split = this_private.unformat_my_post_key.call(self, event.key);
						self.unmark_post_as_me(key_split[0], key_split[1], key_split[2], false);
					}
				},

				format_my_post_key: function (board, thread_id, post_id) {
					return post_id + "_" + thread_id + "_" + board;
				},
				unformat_my_post_key: function (post_key) {
					// returns [board, thread_id, post_id]
					var key_split = post_key.split("_");

					var post_id = parseInt(key_split[0]);
					var thread_id = parseInt(key_split[1]);

					key_split.splice(0, 2);
					var board = key_split.join("_");

					return [board,thread_id,post_id];
				},

				thread_add_sort_function: function (existing_thread, inserting_thread) {
					return (existing_thread.id < inserting_thread.id);
				},

				incomplete_reference_update_function: function (ref, target_post, origin_post) {
					// Complete
					ref.complete(target_post);

					// Stylize / fix target
					origin_post.update_reference_style(ref);
				},
				reference_forwards_death_update_function: function (ref, skip, original_post) {
					// Nothing
				},

				on_post_load_title_update: function (event, self) {
					if (event.post.op) {
						self.format_title();
						self.off("post_load", this_private.on_post_load_title_update);
					}
				},

				on_location_change: function (event, self) {
					if (event.location.fragment !== null && /[0-9]+/.test(event.location.fragment)) {
						// Highlight post with this id
						var post_id = parseInt(event.location.fragment) || 0;

						// Find posts
						if ("self_init" in event) {
							// Set highlighting target
							self.post_highlight_id_target = post_id;
							self.on("post_load", this_private.on_post_load_highlight, self);
							self.on("complete", this_private.on_complete_highlight, self);
						}
						else {
							var c = self.main_context.html.find('.thread>.post_container[post_id="' + post_id + '"]');
							if (c.length > 0) {
								self.main_context.html.find(".thread>.post_container.highlighted").removeClass("highlighted");
								c = c.first();
								c.addClass("highlighted");
								if (!event.is_new) {
									// Scroll to
									xch.Post.prototype.scroll_to.call(null, c);
								}
							}
						}
					}
					else {
						if (!("self_init" in event)) {
							// Un-highlight
							self.main_context.html.find(".thread>.post_container.highlighted").removeClass("highlighted");
						}
					}
				},
				on_post_load_highlight: function (event, self) {
					if (event.context != self.main_context) return;
					if (event.post.id == self.post_highlight_id_target) {
						// Un-highlight others
						self.main_context.html.find(".thread>.post_container.highlighted").removeClass("highlighted");
						// Highlight
						var c = event.post.instances[0].container;
						c.addClass("highlighted");
						// Scroll to
						event.post.scroll_to(c);
						// Disable events
						self.post_highlight_id_target = -1;
						self.off("post_load", this_private.on_post_load_highlight);
						self.off("complete", this_private.on_complete_highlight);
					}
				},
				on_complete_highlight: function (event, self) {
					if (event.context != self.main_context) return;
					// Disable events
					self.post_highlight_id_target = -1;
					self.off("post_load", this_private.on_post_load_highlight);
					self.off("complete", this_private.on_complete_highlight);
				},

				update_sourcing: function () {
					var src = settings.sourcing.source_text;
					this.sourcing = [];

					var tree = [{
						options: this.sourcing
					}];

					src = src.split(/\r?\n|\r/);
					for (var i = 0, j; i < src.length; ++i) {
						// Split
						var start = 0, pos = 0;
						var temp = [], val, esc = false, key;
						while (pos >= 0) {
							pos = src[i].indexOf(";", pos);
							if (pos >= 0) {
								if (pos == 0 || src[i][pos - 1] != "\\") {
									val = src[i].substr(start, pos - start);
									if (esc) {
										val = val.replace("\\;", ";");
										esc = false;
									}
									temp.push(val);
									start = ++pos;
								}
								else {
									esc = true;
									++pos;
								}
							}
							else {
								val = (start > 0 ? src[i].substr(start) : src[i]);
								if (esc) {
									val = val.replace("\\;", ";");
									esc = false;
								}
								if (val.length > 0) temp.push(val);
								break;
							}
						}

						// Skip empty lines
						if (temp.length == 0) continue;

						// Depth check
						var depth = 0;
						for (j = 0; j < temp[0].length; ++j) {
							if (temp[0][j].charCodeAt(0) <= 32) continue;
							else if (temp[0][j] == "#") {
								j = -1;
								break;
							}
							else if (temp[0][j] == ">") {
								++depth;
							}
							else {
								if (j > 0) temp[0] = temp[0].substr(j);
								break;
							}
						}
						if (j < 0) continue;

						// Create new
						var opt = {
							url: temp[0],
							text: "",
							options: [],
							allow_if_spoiler: 3
						};
						for (j = 1; j < temp.length; ++j) {
							key = temp[j].substr(0, pos = (temp[j].indexOf(":")));
							val = temp[j].substr(pos + 1);

							if (key == "text") opt.text = val;
							else if (key == "if_spoiler") {
								if (!xch.string_to_truth(val)) opt.allow_if_spoiler &= ~1;
							}
							else if (key == "if_not_spoiler") {
								if (!xch.string_to_truth(val)) opt.allow_if_spoiler &= ~2;
							}
						}
						if (opt.text.length == 0) {
							var m1, m2;
							if ((m1 = /(?:.+?\:)?\/\/(.+?)\/(?:.*?)/.exec(opt.url)) && (m2 = /([^\.]+)(?:\.[^\.]+)$/.exec(m1[1]))) {
								opt.text = m2[1];
							}
							else {
								opt.text = "text";
							}
						}

						// Add
						if (depth >= tree.length) depth = tree.length - 1;
						tree[depth].options.push(opt);
						++depth;
						tree.splice(depth, tree.length - depth, opt);
					}
				},
				sourcing_format_function: function (fmt, post) {
					if (fmt == "board") {
						fmt = post.thread.board;
					}
					else if (fmt == "thread_id") {
						fmt = post.thread.id;
					}
					else if (fmt == "post_id") {
						fmt = post.id;
					}
					else if (fmt == "thumbnail") {
						if (post.image != null && post.image.thumbnail != null) {
							fmt = post.image.thumbnail;
						}
					}
					else if (fmt == "image") {
						if (post.image != null && post.image.url != null) {
							fmt = post.image.url;
						}
					}

					return fmt;
				},

				spawn_delete_report_popup: function (report, title, description, action, action_data) {
					var data = { self: this, action: action, action_data: action_data, report: (report !== null) };

					var params = {
						title: title,
						text: description,
						size: {
							width: 320
						},
						buttons: [{
							text: "Cancel",
							on: {
								click: {
									callback_data: data,
									callback: function (event) {
										if (event.which != 1) return true;

										event.data.popup.close();
									}
								}
							}
						}, {
							text: (data.report ? "Report" : "Delete"),
							on: {
								click: {
									callback_data: data,
									callback: function (event) {
										if (event.which != 1) return true;

										var input_val = (event.data.popup.text_input ? event.data.popup.text_input.val() : null);

										if (event.data.callback_data.action) {
											close = event.data.callback_data.action.call(event.data.callback_data.self, event.data.callback_data.action_data, input_val);
										}

										if (close) {
											event.data.popup.close();
										}
									}
								}
							}
						}],
						on: {
							close: {
								callback_data: data,
								callback: function (event, callback_data) {
									callback_data.self.delete_report_popup = null;
								}
							}
						}
					};

					// Add textarea
					if (data.report) {
						params.textarea = {
							value: report,
							single_line: true
						};
					}

					// Create
					this.delete_report_popup = new xch.Popup(params);

					// Select
					if (data.report) {
						xch.set_selection(this.delete_report_popup.text_input, 0, report.length);
					}
				},
				spawn_delete_report_message: function (report, post, delete_image) {
					var is_report = (report !== null);

					// Create message
					var pb, pb_inner, message_text;
					var msg = new xch.Message({
						type: "info",
						title: (is_report ? "Reporting Post" : "Deleting Post"),
						html: (style.e("div").append(
							(message_text = style.e("div"))
							.text(is_report ? "Post is being reported..." : (delete_image ? "Image is being deleted..." : "Post is being deleted..."))
						).append(
							(pb = style.e("div", "message_progress default"))
							.append(
								(pb_inner = style.e("div", "message_progress_inner active"))
							)
						)),
						closeable: false,
						life: 0
					});

					// Create form data
					var fd = new FormData();
					var target_post_url;

					// Fields
					var password;
					fd.append("board", post.thread.board);
					fd.append("delete_" + post.id, "on");
					if (is_report) {
						fd.append("reason", report);
						fd.append("password", this.qr.create_password(8)); // Using a random password, becuase why not
						fd.append("report", "Report");

						target_post_url = info.create_url.to_report_submit_target(post.thread.board, post.thread.id);
					}
					else {
						var my_post_data = this.posted_by_me_data(post);
						if (my_post_data == null) {
							password = this.qr.saved_post_settings.password || "";
						}
						else {
							password = my_post_data.password;
						}

						if (delete_image) fd.append("file", "on");
						fd.append("password", password);
						fd.append("delete", "Delete");

						target_post_url = info.create_url.to_delete_submit_target(post.thread.board, post.thread.id);
					}

					// Ajax request
					var ajax_data = {
						url: target_post_url,
						method: "POST",
						return_type: "text",
						post_data: fd,
						//cred: true,
						on: {
							load: this_private.report_delete_on_load,
							progress: this_private.report_delete_on_progress,
							error: this_private.report_delete_on_error,
							abort: this_private.report_delete_on_abort
						},
						event_data: {
							self: this,
							message: msg,
							progress_bar: pb,
							progress_inner: pb_inner,
							message_text: message_text,
							is_report: is_report,
							delete_image: delete_image,
							post: post
						}
					};

					// Submit
					xch.ajax(ajax_data);
				},

				report_delete_on_load: function (event) {
					// Change
					if (event.okay) {
						// Parse response
						var html = xch.string_to_dom(event.response);
						// Check
						if (html != null && (html = $(html)).length > 0) {
							// Parse
							var error = event.data.event_data.self.check_html_for_error(html);
							if (error === null) {
								// Okay; done
								this_private.report_delete_success.call(event.data.event_data.self, event.data.event_data);
							}
							else {
								// Remove last "."
								if (error[error.length - 1] == "." && error.substr(-2) != "..") {
									error = error.substr(0, error.length - 1);
								}

								this_private.report_delete_error.call(event.data.event_data.self,
									event.data.event_data,
									error
								);
							}
						}
						else {
							// Bad
							this_private.report_delete_error.call(event.data.event_data.self,
								event.data.event_data,
								"An error occured while parsing the resulting HTML"
							);
						}
					}
					else {
						// Error
						this_private.report_delete_error.call(event.data.event_data.self,
							event.data.event_data,
							"An invalid http status was returned (" + event.status + ", " + event.status_text + ")"
						);
					}

				},
				report_delete_on_progress: function (event) {
					// Percent update
					var perc = event.event.loaded / event.event.total;
					event.data.event_data.progress_inner.css("width", (perc * 100).toFixed(2) + "%");
				},
				report_delete_on_error: function (event) {
					// Connection error
					this_private.report_delete_error.call(event.data.event_data.self,
						event.data.event_data,
						"A connection error occured"
					);
				},
				report_delete_on_abort: function (event) {
					// Abort
					this_private.report_delete_error.call(event.data.event_data.self,
						event.data.event_data,
						"The submission was aborted"
					);
				},

				report_delete_success: function (event_data) {
					event_data.message.set_type("okay");
					event_data.message.container.find(".message_title").text(event_data.is_report ? "Report Successful" : "Delete Successful");
					event_data.message_text.text(event_data.is_report ? "Post was successfully reported" : (event_data.delete_image ? "Image was successfully deleted" : "Post was successfully deleted"));
					event_data.progress_bar.addClass("hidden");

					event_data.message.set_closeable(true);
					event_data.message.set_lifetime();

					// If this was a delete, update deleted status
					if (!event_data.is_report) {
						if (event_data.delete_image) {
							event_data.post.mark_as_dead_image(false);
						}
						else {
							event_data.post.mark_as_dead(false);
						}
					}
				},
				report_delete_error: function (event_data, error_message) {
					event_data.message.set_type("error");
					event_data.message.container.find(".message_title").text(event_data.is_report ? "Reporting Error" : "Deletion Error");
					event_data.message_text.text(error_message);
					event_data.progress_bar.addClass("hidden");

					event_data.message.set_closeable(true);
					event_data.message.set_lifetime();
				},

				spawn_post_marking_popup: function (title, description, button, action, action_data) {
					var data = { self: this, action: action, action_data: action_data };

					var params = {
						title: title,
						text: description,
						size: {
							width: 320
						},
						buttons: [{
							text: "Cancel",
							on: {
								click: {
									callback_data: data,
									callback: function (event) {
										if (event.which != 1) return true;

										event.data.popup.close();
									}
								}
							}
						}, {
							text: button,
							on: {
								click: {
									callback_data: data,
									callback: function (event) {
										if (event.which != 1) return true;

										var input_val = (event.data.popup.text_input ? event.data.popup.text_input.val() : null);

										if (event.data.callback_data.action) {
											close = event.data.callback_data.action.call(event.data.callback_data.self, event.data.callback_data.action_data, input_val);
										}

										if (close) {
											event.data.popup.close();
										}
									}
								}
							}
						}],
						on: {
							close: {
								callback_data: data,
								callback: function (event, callback_data) {
									callback_data.self.post_marking_popup = null;
								}
							}
						}
					};

					// Create
					this.post_marking_popup = new xch.Popup(params);
				},

				sourcing_info_build_menu_options: function (opts, post) {
					var text, href, sp_allow, options = [];

					for (var i = 0; i < opts.length; ++i) {
						// Spoiler allowing
						sp_allow = (post.image == null ? true : ((opts[i].allow_if_spoiler & (post.image.spoiler ? 1 : 2)) != 0));
						if (sp_allow) {
							// Format values
							text = xch.format_string(opts[i].text, this_private.sourcing_format_function, post);
							href = xch.format_string(opts[i].url, this_private.sourcing_format_function, post);
							// Add
							var option = {
								html: (
									style.e("a")
									.attr("target", "_blank")
									.attr("href", href)
									.text(text)
								),
								on: {
									click: function (event) {
										if (event.which != 1) return true;
										event.data.option.menu.close();
										return true;
									}
								}
							};

							// Recursive options
							if (opts[i].options.length > 0) {
								option.options = this_private.sourcing_info_build_menu_options.call(this, opts[i].options, post);
							}

							// Add
							options.push(option);
						}
					}

					// Done
					return options;
				},
				sourcing_info_click: function (event) {
					if (event.which != 1) return true;

					// Create menu
					var opts = content.sourcing[event.data.sourcing_id].options;
					if (opts && opts.length > 0) {
						// Create menu
						if (event.data.menu == null) {
							var options = this_private.sourcing_info_build_menu_options.call(this, opts, event.data.post);

							// Create
							if (options.length > 0) {
								event.data.menu = new xch.Menu($(this), {
									close_on_mouseleave: false,
									fit_width_min: true,
									keep_on_screen: true,
									menu_class: "small",
									fixed_z: null,
									open: "down",
									on: {
										close: {
											callback_data: event.data,
											callback: this_private.sourcing_info_on_menu_close
										}
									},
									options: options
								});
							}
						}
						else {
							// Close
							event.data.menu.close();
						}

						// Done
						return false;
					}
					return true;
				},
				sourcing_info_on_menu_close: function (event, event_data) {
					// Nullify
					event_data.menu = null;
				},

				delete_post_image_action: function (post) {
					this.delete_post_image(post, true);
					return true;
				},
				delete_post_action: function (post) {
					this.delete_post(post, true);
					return true;
				},
				report_post_action: function (post, input_val) {
					if (input_val.trim().length == 0) return false;
					this.report_post(post, input_val, true);
					return true;
				},
				mark_post_as_me_ask_action: function (data) {
					this.mark_post_as_me_ask(data.post, data.mark, true);
					return true;
				}

			};

			Content.prototype = {

				constructor: Content,

				init: function (context) {
					// Get container
					this.container = context.html.find('form[name="postcontrols"]').first();

					// Get the favicon node
					this.favicon_node = context.html.find('head>link[rel="shortcut icon"]');
					if (this.favicon_node.length == 0) this.favicon_node = null;
					this.set_favicon(info.favicons.alive.standard);

					// Remove stuff
					if (this.container.length > 0) {
						// Remove delete options
						this.container.children(".delete,input").remove();

						// Replace
						var cont = this.container.contents();
						var c = this.container;
						c.replaceWith(
							(this.container = style.e("div", "content"))
							.html(cont)
						);
					}

					// Add floating inline container
					context.html.find("body").append(
						(this.floating_container = style.e("div", "floating_inline_container"))
					);

					// Create header
					this.header = new xch.Header(context.html);
					// Create quick reply
					this.qr = new xch.QuickReply(context.html);
					// Create updater
					this.updater = new xch.Updater(info.index ? null : context.html);

					// Title
					if (info.index) {
						this.format_title();
					}
				},
				parse: function (context) {
					var self = this;

					if (context.is_new) {
						// Init
						this.init(context);
					}

					// Parse queue
					var parse_queue = [];

					// Find threads
					var html_threads = context.html.find('div[id^="thread_"]');
					for (var i = 0; i < html_threads.length; ++i) {
						var obj = $(html_threads[i]), existing, thread_context;
						// Append new thread
						var thread = new xch.Thread();
						thread.init(obj, context);
						// Check for duplicate
						if (context.is_new || (existing = this.get_thread(thread.id, thread.board)) == null) {
							// New
							thread_context = [
								thread,
								obj,
								{
									is_new: true,
									posts_loaded: [],
									posts_total: 0
								}
							];
							this.thread_new(thread, context, thread_context[2]);
						}
						else {
							// Update
							thread_context = [
								existing,
								obj,
								{
									is_new: false,
									posts_loaded: [],
									posts_total: 0,
								}
							];
							// Trigger event
							this.thread_new(existing, context, thread_context[2]);
							// Update any settings
							existing.update_with(thread, context, thread_context[2]);
						}
						parse_queue.push(thread_context);
					}

					// Start thread parsing
					context.threads_total = parse_queue.length;
					for (var i = 0, j = parse_queue.length, p_q; i < j; ++i) {
						p_q = parse_queue[i];
						p_q[0].parse(p_q[1], context, p_q[2]);
					}

					// Complete
					if (parse_queue.length == 0) {
						this.trigger("complete", {
							context: context
						});
					}
				},

				thread_new: function (thread, context, thread_context) {
					if (thread_context.is_new) {
						// Add
						xch.insert_element_sorted(this.threads, thread, this_private.thread_add_sort_function);
						// Map
						if (!(thread.board in this.board_map)) {
							// New
							this.board_map[thread.board] = {
								thread_map: {}
							};
						}
						var b = this.board_map[thread.board];
						// Add
						b.thread_map[thread.id] = thread;
					}
					// Signal
					this.trigger("thread_new", {
						thread: thread,
						context: context,
						thread_context: thread_context
					});
				},
				thread_ready: function (thread, context, thread_context) {
					// Signal
					this.trigger("thread_ready", {
						thread: thread,
						context: context,
						thread_context: thread_context
					});
				},
				thread_loaded: function (thread, context, thread_context) {
					// Dead posts
					var i, j, m, n;
					/*!debug!*/var dead_count = 0;
					for (i = 0, m = thread.posts.length, n = thread_context.posts_loaded.length; i < m; ++i) {
						// Skip if already deleted
						if (thread.posts[i].deleted) continue;

						// Check if exists
						for (j = 0; j < n; ++j) {
							if (thread.posts[i] == thread_context.posts_loaded[j]) break;
						}

						// Deleted
						if (j == n) {
							/*!debug!*/++dead_count;
							// Dead post
							thread.posts[i].mark_as_dead(true);
							// Event
							this.trigger("post_delete", {
								post: thread.posts[i],
								context: context,
								thread_context: thread_context
							});
						}
					}
					/*!debug!*/if (dead_count > 0) {console.log(context.html.html());}

					// Forwards dead post checking
					for (i = 0, j = this.incomplete_references.length; i < j; ++i) {
						if (this.incomplete_references[i].origin.thread == thread && this.incomplete_references[i].forwards_death()) {
							// Update html
							this.incomplete_references[i].origin.update_reference(this.incomplete_references[i], this_private.reference_forwards_death_update_function, null);
							// Move to dead
							this.incomplete_references.splice(i, 1);
							this.add_dead_reference(this.incomplete_references[i]);
							--i;
							--j;
						}
					}

					// Trigger event
					this.trigger("thread_load", {
						thread: thread,
						context: context,
						thread_context: thread_context
					});
					// Complete?
					if (++context.threads_loaded == context.threads_total) {
						this.trigger("complete", {
							context: context
						});
					}
				},
				thread_update: function (thread, context, thread_context, state_old) {
					// Trigger event
					this.trigger("thread_update", {
						thread: thread,
						context: context,
						thread_context: thread_context,
						state_old: state_old
					});
				},
				thread_delete: function (board, thread_id) {
					var thread = null;

					if (board in this.board_map) {
						if (thread_id in this.board_map[board].thread_map) {
							thread = this.board_map[board].thread_map[thread_id];
						}
					}

					if (thread != null && !thread.deleted) {
						// Mark as deleted
						thread.deleted = true;

						// Signal
						this.trigger("thread_delete", {
							thread: thread
						});
					}
				},

				add_incomplete_reference: function (reference) {
					this.incomplete_references.push(reference);
				},
				check_for_incomplete_references: function (post) {
					for (var i = 0, j = this.incomplete_references.length, ref; i < j; ++i) {
						ref = this.incomplete_references[i];
						if (ref.target_post_id == post.id && ref.target_board == post.thread.board) {
							if (ref.target_thread_id === null) {
								if (post.thread.id == ref.origin.thread.id) {
									// Okay
									ref.origin.update_reference(ref, this_private.incomplete_reference_update_function, post);
									// Next
									this.incomplete_references.splice(i, 1);
									--i;
									--j;
								}
							}
							else if (ref.target_thread_id == post.thread.id) {
								// Okay
								ref.origin.update_reference(ref, this_private.incomplete_reference_update_function, post);
								// Next
								this.incomplete_references.splice(i, 1);
								--i;
								--j;
							}
						}
					}
				},

				add_dead_reference: function (reference) {
					this.dead_references.push(reference);
				},

				change_incomplete_to_dead_reference: function (reference) {
					for (var i = 0, j = this.incomplete_references.length; i < j; ++i) {
						if (this.incomplete_references[i] == reference) {
							// Dead
							this.add_dead_reference(reference);
							// Next
							this.incomplete_references.splice(i, 1);
							--i;
							--j;
						}
					}
				},

				get_thread: function (thread_id, board) {
					if (board in this.board_map) {
						var m = this.board_map[board].thread_map;
						if (thread_id in m) return m[thread_id];
					}
					return null;
				},
				get_post: function (post_id, board) {
					var p;
					for (var i = 0; i < this.threads.length; ++i) {
						if (this.threads[i].board == board && (p = this.threads[i].get_post(post_id)) != null) return p;
					}
					return null;
				},

				format_title: function (format, prefix, suffix) {
					if (!format) {
						format = (info.index ? this.title_format.index : this.title_format.thread);
					}

					format = this.header.format_board_info(format);

					this.main_context.html.find("head>title").html((prefix || "") + format + (suffix || ""));
				},

				on: function (event, callback, callback_data) {
					if (event in this.events) {
						this.events[event].callbacks.push([callback, callback_data]);
					}
				},
				off: function (event, callback) {
					if (event in this.events) {
						var e = this.events[event];
						if (e.triggering) {
							if (e.removals == null) e.removals = [];
							e.removals.push(callback);
						}
						else {
							e = e.callbacks;
							for (var i = 0; i < e.length; ++i) {
								if (e[i][0] == callback) {
									e.splice(i, 1);
									--i;
								}
							}
						}
					}
				},
				trigger: function (event, data) {
					// Trigger an event
					var e = this.events[event];
					/*!debug!*/assert(e, "Bad event [" + event + "]");
					e.triggering = true;
					for (var i = 0, j = e.callbacks.length; i < j; ++i) {
						/*!debug!*/assert(e.callbacks[i], "Bad event in [" + event + "]; " + i + "/" + j+"; "+e.callbacks[i][0].toString());
						/*!debug!*/try {
						e.callbacks[i][0].call(this, data, e.callbacks[i][1], event);
						/*!debug!*/}
						/*!debug!*/catch (err) {
						/*!debug!*/	assert(false, "Bad event in [" + event + "] (" + err + "); " + i + "/" + j+"; "+e.callbacks[i][0].toString());
						/*!debug!*/}
					}
					e.triggering = false;
					if (e.removals != null) {
						for (var i = 0; i < e.removals.length; ++i) {
							this.off(event, e.removals[i]);
						}
						e.removals = null;
					}
				},

				mark_post_as_me: function (post, source, password, save) {
					var post_key = this_private.format_my_post_key.call(this, post.thread.board, post.thread.id, post.id);

					this.my_posts[post_key] = {
						name: (source ? source.name.value : null),
						password: password,
						m_time: (new Date()).getTime(),
						s_time: post.timestamp
					};

					// Save
					if (save) {
						this_private.save_my_post.call(this, post_key, true);
					}

					// Update references
					post.update_all_reply_references();
					post.update_all_backlink_references();
				},
				unmark_post_as_me: function (board, thread_id, post_id, save) {
					// Arguments can also be: (post, save)
					var post_key, post = null;

					if (arguments.length > 0 && (arguments[0] instanceof xch.Post)) {
						post = arguments[0];
						post_key = this_private.format_my_post_key.call(this, post.thread.board, post.thread.id, post.id);

						save = arguments[1];
					}
					else {
						post_key = this_private.format_my_post_key.call(this, board, thread_id, post_id);
						if (
							board in this.board_map &&
							thread_id in this.board_map[board].thread_map &&
							post_id in this.board_map[board].thread_map[thread_id].post_map
						) {
							post = this.board_map[board].thread_map[thread_id].post_map[post_id];
						}
					}

					if (post_key in this.my_posts) {
						delete this.my_posts[post_key];

						if (save) {
							this_private.save_my_post.call(this, post_key, false);
						}
					}

					// Update references
					if (post != null) {
						post.update_all_reply_references();
						post.update_all_backlink_references();
					}
				},
				posted_by_me: function (board, thread_id, post_id) {
					if (arguments[0] instanceof xch.Post) {
						board = arguments[0].thread.board;
						thread_id = arguments[0].thread.id;
						post_id = arguments[0].id;
					}

					var post_key = this_private.format_my_post_key.call(this, board, thread_id, post_id);
					return (post_key in this.my_posts);
				},
				posted_by_me_data: function (board, thread_id, post_id) {
					if (arguments[0] instanceof xch.Post) {
						board = arguments[0].thread.board;
						thread_id = arguments[0].thread.id;
						post_id = arguments[0].id;
					}

					var post_key = this_private.format_my_post_key.call(this, board, thread_id, post_id);
					return (post_key in this.my_posts ? this.my_posts[post_key] : null);
				},

				delete_post_image: function (post, auto) {
					if (auto) {
						// Delete
						this_private.spawn_delete_report_message.call(this, null, post, true);
					}
					else {
						// Confirm delete
						if (this.delete_report_popup != null && this.delete_report_popup.open) return;

						this_private.spawn_delete_report_popup.call(this,
							null,
							"Deleting Image",
							"Are you sure you want to delete the image of >>" + post.id + "?",
							this_private.delete_post_image_action,
							post
						);
					}
				},
				delete_post: function (post, auto) {
					if (auto) {
						// Delete
						this_private.spawn_delete_report_message.call(this, null, post, false);
					}
					else {
						// Confirm delete
						if (this.delete_report_popup != null && this.delete_report_popup.open) return;

						this_private.spawn_delete_report_popup.call(this,
							null,
							"Deleting Post",
							"Are you sure you want to delete >>" + post.id + "?",
							this_private.delete_post_action,
							post
						);
					}
				},
				report_post: function (post, reason, auto) {
					if (auto) {
						// Report
						this_private.spawn_delete_report_message.call(this, reason || "", post, false);
					}
					else {
						// Confirm report
						if (this.delete_report_popup != null && this.delete_report_popup.open) return;

						this_private.spawn_delete_report_popup.call(this,
							reason || "",
							"Reporting Post",
							"Enter a reason why you're reporting >>" + post.id + ":",
							this_private.report_post_action,
							post
						);
					}
				},
				mark_post_as_me_ask: function (post, mark, auto) {
					if (auto) {
						// Unmark
						if (mark) {
							this.mark_post_as_me(post, null, null, true);
						}
						else {
							this.unmark_post_as_me(post, true);
						}
						// Message
						new xch.Message({
							type: "okay",
							title: "Post " + (mark ? "Marked" : "Unmarked"),
							html: (
								style.e("div")
								.append(style.t("Post "))
								.append(
									style.e("a", "message_link")
									.text(">>" + (post.id))
									.attr("href", info.create_url.to_post(post.thread.board, post.thread.id, post.id))
								)
								.append(style.t(" has been " + (mark ? "marked" : "unmarked") + " as yours"))
							)
						});
					}
					else {
						// Confirm delete
						if (this.post_marking_popup != null && this.post_marking_popup.open) return;

						this_private.spawn_post_marking_popup.call(this,
							(mark ? "Mark" : "Unmark") + " Post",
							"Are you sure you want to " + (mark ? "mark" : "unmark") + " >>" + post.id + " as yours?",
							(mark ? "Mark" : "Unmark"),
							this_private.mark_post_as_me_ask_action,
							{
								post: post,
								mark: mark
							}
						);
					}
				},

				set_favicon: function (url) {
					if (this.favicon_node != null) {
						this.favicon_node.remove();
					}
					this.main_context.html.find("head").append(
						(this.favicon_node = style.e("link"))
						.attr("rel", "shortcut icon")
						.attr("href", url)
					);
				},

				append_sourcing_info: function (container, post) {
					var text, href, sp_allow;
					for (var i = 0; i < this.sourcing.length; ++i) {
						// Spoiler allowing
						sp_allow = (post.image == null ? true : ((this.sourcing[i].allow_if_spoiler & (post.image.spoiler ? 1 : 2)) != 0));
						if (sp_allow) {
							// Format values
							text = xch.format_string(this.sourcing[i].text, this_private.sourcing_format_function, post);
							href = xch.format_string(this.sourcing[i].url, this_private.sourcing_format_function, post);
							// Add
							container.append(
								style.e("a", "post_file_info_extra_link post_file_info_extra_link_sourcing")
								.attr("sourcing_id", i)
								.attr("target", "_blank")
								.attr("href", href)
								.text(text)
							);
						}
					}
				},
				prepare_sourcing_info: function (container, post, instance) {
					// Update events
					var sl = container.children(".post_file_info_extra_link_sourcing");
					var sli, sid, data
					for (var i = 0; i < sl.length; ++i) {
						sli = $(sl[i]);
						sid = sli.attr("sourcing_id");
						if (sid !== undefined) {
							sid = parseInt(sid) || 0;
							if (sid >= 0 && sid < this.sourcing.length && this.sourcing[sid].options.length > 0) {
								// Bind event
								data = { content: this, post: post, instance: instance, sourcing_id: sid, menu: null };
								instance.bind_html_event(
									sli,
									"click", data, this_private.sourcing_info_click, false
								);
							}
						}
					}
				},

				check_html_for_error: function (html) {
					if ((html.find("title").html() || "").toLowerCase().trim() == "error") {
						var error = false;
						var h1s = html.find("header>h1");
						for (var i = 0; i < h1s.length; ++i) {
							if (($(h1s[i]).html() || "").toLowerCase().trim() == "error") {
								error = true;
							}
						}
						if (error) {
							error = html.find("div>h2");
							if (error.length > 0) {
								error = (error.html() || "").trim();
							}
							else {
								error = "Unknown error.";
							}
							return error;
						}
					}
					if (html.find("body>header").length == 0 || html.find("body>footer").length == 0) {
						return "Document not fully loaded.";
					}
					return null;
				},

				on_get_posts: function () {
					var posts = [];

					for (var i = 0, j, t_posts; i < this.threads.length; ++i) {
						t_posts = this.threads[i].posts;
						for (j = 0; j < t_posts.length; ++j) {
							if (t_posts[j].loaded()) {
								posts.push(t_posts[j]);
							}
						}
					}

					return posts;
				}

			};

			Content.Context = (function () {

				var Context = function (local_settings) {
					if (!local_settings) local_settings = {};

					this.info = local_settings.info || null;
					this.html = local_settings.html || null;
					this.is_new = local_settings.is_new || false;
					this.stylize = local_settings.stylize || false;
					this.html_target = local_settings.html_target || null;
					this.post_queue = (local_settings.post_queue ? local_settings.post_queue : new xch.PostQueue(settings.post.fetching.parsing_group.size, settings.post.fetching.parsing_group.interval));
					this.loader = local_settings.loader || null;

					this.threads_loaded = 0;
					this.threads_total = 0;
				};

				return Context;

			})();

			return Content;

		})(),

		// Thread and its posts
		Thread: (function () {

			var Thread = function () {
				this.id = null;
				this.posts = [];
				this.post_map = {};
				this.sticky = false;
				this.locked = false;
				this.board = null;
				this.posts_omitted = 0;
				this.images_omitted = 0;
				this.post_earliest = null;
				this.post_latest = null;
				this.visible = false;
				this.deleted = false;

				this.container = null;
			};

			var this_private = {

				post_add_sort_function: function (existing_post, inserting_post) {
					return (inserting_post.id > existing_post.id);
				}

			};

			Thread.prototype = {

				constructor: Thread,

				init: function (html, context) {
					var self = this;

					// Visible
					this.visible = context.is_new;

					// Info
					this.board = context.info.board;

					// OP post
					var op = html.find("div.post.op");

					// Omitted
					var om = op.find(".omitted");
					if (om.length > 0) {
						om = $(om[0]).text();
						om = /([0-9]+)\spost(s)?(\sand\s([0-9]+)\simage\srepl(y|ies)\somitted)?/i.exec(om);
						if (om) {
							this.posts_omitted = parseInt(om[1] || "0") || 0;
							this.images_omitted = parseInt(om[4] || "0") || 0;
						}
					}

					// Id
					var intro = op.find(".intro");
					this.id = parseInt((intro.find(".post_no:nth-of-type(2)").html() || "").trim()) || 0;

					// Icons
					var icons = intro.find(".icon");
					for (var i = 0; i < icons.length; ++i) {
						var icon = $(icons[i]);
						if (icon.hasClass("icon-pushpin")) {
							this.sticky = true;
						}
						else if (icon.hasClass("icon-lock")) {
							this.locked = true;
						}
					}

					// Container
					this.container = html;
				},
				parse: function (html, context, thread_context) {
					var self = this;

					// Stylize
					if (context.is_new && context.stylize) {
						html = this.stylize(html);
					}

					// Parse queue
					var parse_queue_new = [];
					var parse_queue_old = (settings.post.fetching.new_priority ? [] : parse_queue_new);
					var parse_queue_length = 0;

					// Create posts
					var html_posts = html.find("div.post");
					for (var i = 0; i < html_posts.length; ++i) {
						var obj = $(html_posts[i]), existing;
						// Append
						var post = new xch.Post(this), par;
						post.init(obj, context);
						// Check for duplicate
						if (thread_context.is_new || (existing = this.get_post(post.id)) == null) {
							// New
							this.add_post(post, context, thread_context);
							parse_queue_new.push([
								post,
								obj,
								{
									is_new: true
								}
							]);
							if ((par = obj.parent())[0] != html[0]) {
								if (par.hasClass("post")) {
									par.children(".body").nextAll().remove();
									if (par.prev().prop("tagName") != "BR") {
										par.before(document.createElement("br"));
									}
								}
								html.append(document.createElement("br"));
								html.append(obj);
							}
						}
						else {
							// Update
							parse_queue_old.push([
								existing,
								obj,
								{
									is_new: false
								}
							]);
						}
						++parse_queue_length;
					}

					// Remove unnecessaries
					if (context.is_new && context.stylize) {
						// Remove anything buggy
						html.children(":not(.post):not(.fileinfo):not(br):not(a:nth-of-type(1)):not(img:nth-of-type(1))").remove();
					}

					// Set
					thread_context.posts_total = parse_queue_length;

					// Thread ready
					content.thread_ready(this, context, thread_context);

					// Start post parsing
					var i, j, p_q;
					for (i = 0, j = parse_queue_new.length, p_q; i < j; ++i) {
						p_q = parse_queue_new[i];
						p_q[0].parse(p_q[1], context, thread_context, p_q[2]);
					}
					for (i = 0, j = parse_queue_old.length, p_q; i < j; ++i) {
						p_q = parse_queue_old[i];
						p_q[0].parse(p_q[1], context, thread_context, p_q[2]);
					}

					// Fully loaded?
					if (thread_context.posts_total == 0) {
						content.thread_loaded(this, context, thread_context);
					}
				},
				add_post: function (post, context, thread_context) {
					// Add
					var post_n = xch.insert_element_sorted(this.posts, post, this_private.post_add_sort_function);
					post.t_id = post_n;
					// Update IDs after
					while (++post_n < this.posts.length) {
						// This loop never runs unless inserting posts out of order, so this doesn't consume any noteworthy time
						this.posts[post_n].t_id = post_n;
					}
					// Add to map
					this.post_map[post.id] = post;
					// Earliest/latest
					if (context.is_new && !post.op) {
						if (this.post_earliest == null || post.id < this.post_earliest) {
							this.post_earliest = post;
						}
						if (this.post_latest == null || post.id > this.post_latest) {
							this.post_latest = post;
						}
					}
					// Signal
					content.trigger("post_new", {
						post: post
					});
				},

				update_with: function (other_thread, context, thread_context) {
					// Old state
					var changed = false;
					var state_old = {
						sticky: this.sticky,
						locked: this.locked
					};

					// Compare settings, update locked/sticky status
					if (this.sticky != other_thread.sticky) {
						this.sticky = other_thread.sticky;
						changed = true;
					}
					if (this.locked != other_thread.locked) {
						this.locked = other_thread.locked;
						changed = true;
					}

					// Event
					if (changed) {
						content.thread_update(this, context, thread_context, state_old);
					}
				},

				post_loaded: function (post, context, thread_context, post_context, updated, update_data) {
					// Add
					thread_context.posts_loaded.push(post);
					// Events
					content.trigger("post_load", {
						post: post,
						context: context,
						thread_context: thread_context,
						post_context: post_context,
						updated: updated,
						update_data: (updated ? update_data : null)
					});
					// Fully loaded?
					if (thread_context.posts_loaded.length == thread_context.posts_total) {
						content.thread_loaded(this, context, thread_context);
					}
				},
				post_updated: function (post, update_data) {
					// Events
					content.trigger("post_load", {
						post: post,
						context: null,
						thread_context: { is_new: false },
						post_context: { is_new: false },
						updated: true,
						update_data: update_data
					});
				},

				ready: function () {
					return (this.id !== null);
				},
				loaded: function () {
					return (this.posts_loaded >= this.posts.length);
				},

				set_expanded: function (expand) {
					if (expand) {
						if (!this.container.hasClass("expanded")) {
							this.container.addClass("expanded");

							// Expand
							var insert_after = this.container.children(".post_container.op").first();

							// Get posts
							var posts = [], max_id = this.post_earliest.id, i, j;
							for (i = 0, j = this.posts.length; i < j; ++i) {
								// Not displayed
								if (this.posts[i].id < max_id && !this.posts[i].op) {
									xch.insert_element_sorted(posts, this.posts[i], this_private.post_add_sort_function);
								}
							}

							// Insert
							for (i = 0, j = posts.length; i < j; ++i) {
								insert_after.after(
									posts[i].instances[0].container
								);
								insert_after = posts[i].instances[0].container;
							}

						}
					}
					else {
						if (this.container.hasClass("expanded")) {
							this.container.removeClass("expanded");

							// Get posts
							var posts = [], max_id = this.post_earliest.id, i, j;
							for (i = 0, j = this.posts.length; i < j; ++i) {
								// Not displayed
								if (this.posts[i].id < max_id && !this.posts[i].op) {
									posts.push(this.posts[i]);
								}
							}

							// Remove
							for (i = 0, j = posts.length; i < j; ++i) {
								posts[i].instances[0].container.detach();
							}

						}
					}
				},

				stylize: function (html) {
					// New container
					var c_inner = style.e("div", "thread");

					// Replace
					this.container.after(c_inner);
					c_inner
					.attr("thread_id", this.id)
					.append(this.container.contents())
					.children("input,br.clear,hr,.delete").remove();
					this.container.remove();
					this.container = c_inner;

					// Remove last br
					c_inner.children("br").last().remove();

					// Return
					return this.container;
				},

				get_post: function (post_id) {
					if (post_id in this.post_map) return this.post_map[post_id];
					return null;
				}

			};

			return Thread;

		})(),

		// Post and its contents
		Post: (function () {

			var Post = function (thread) {
				this.thread = thread;

				this.id = null;
				this.t_id = null;
				this.op = false;
				this.image = null;
				this.timestamp = 0;
				this.comment_truncated = false;
				this.deleted = false;

				this.instances = [ null ];

				this.references = [];
				this.replies = [];
			};

			var events = {

				position_floating_post: function (container, data) {
					// Params
					var me = $(this);
					var win = $(window);
					var area = content.header.get_screen_area();
					area.left += win.scrollLeft();
					area.top += win.scrollTop();
					var this_pos = me.offset();
					var this_width = me.width();

					// Initial position
					var min_left = (data.instance.container.offset().left + style.post.inline.floating.offsets.x);
					var left = min_left;
					var top = (this_pos.top + me.height() + style.post.inline.floating.offsets.y);
					container.css({
						left: left + "px",
						top: top + "px",
						"max-width": ""
					});


					// Horizontal position
					var c_width = container.width();
					var w_change = false;

					// Link bounding
					if (
						left > this_pos.left ||
						left + c_width < this_pos.left + this_width
					) {
						// Center
						left = this_pos.left + (this_width - c_width) / 2;
					}

					// Screen bounding
					if (left + c_width > area.left + area.width) {
						left = area.left + area.width - c_width;
					}
					if (left < min_left) {
						left = min_left;
					}
					if (left < area.left) {
						left = area.left;
					}
					if (left + c_width > area.left + area.width) {
						c_width = area.width + area.left - left;
						w_change = true;
					}

					container.css("left", left + "px");
					if (c_width > 0 && w_change) container.css("max-width", c_width + "px");


					// Vertical position
					var c_height = container.outerHeight();
					var bottom = (this_pos.top - style.post.inline.floating.offsets.y);
					if (
						top + c_height > area.top + area.height &&
						(area.top + area.height - top) < (bottom - area.top) &&
						(bottom - c_height >= area.top)
					) {
						top = bottom - c_height;
						container.css("top", top + "px");
					}
				},
				update_loader_status: function (container, loader, text, progress_selector) {
					var text_str = "";

					switch (loader.status) {
						case xch.Loader.status.NOT_RUNNING:
						{
							text_str = "Loading thread...";

							container.find(progress_selector)
							.removeClass("ajax parse")
							.children()
								.removeClass("parse")
								.addClass("ajax")
								.css("width", "0%");
						}
						break;
						case xch.Loader.status.AJAX:
						{
							text_str = "Loading thread...";

							container.find(progress_selector)
							.removeClass("ajax parse")
							.children()
								.removeClass("parse")
								.addClass("ajax")
								.css("width", (loader.ajax_progress * 100).toFixed() + "%");
						}
						break;
						case xch.Loader.status.ERROR:
						{
							text_str = "Error loading";

							container.find(progress_selector)
							.removeClass("ajax parse")
							.children()
								.removeClass("parse ajax")
								.css("width", "0%");
						}
						break;
						case xch.Loader.status.THREAD_DELETED:
						{
							text_str = "Thread deleted";

							container.find(progress_selector)
							.removeClass("ajax parse")
							.children()
								.removeClass("parse ajax")
								.css("width", "0%");
						}
						break;
						case xch.Loader.status.PARSE_WAITING:
						{
							text_str = "Waiting to parse...";

							container.find(progress_selector)
							.removeClass("parse")
							.addClass("ajax")
							.children()
								.removeClass("ajax")
								.addClass("parse")
								.css("width", "0%");
						}
						break;
						case xch.Loader.status.PARSING:
						{
							text_str = "Parsing...";

							container.find(progress_selector)
							.removeClass("parse")
							.addClass("ajax")
							.children()
								.removeClass("ajax")
								.addClass("parse")
								.css("width", (loader.parse_progress * 100).toFixed() + "%");
						}
						break;
						case xch.Loader.status.COMPLETE:
						{
							text_str = "Complete";

							container.find(progress_selector)
							.removeClass("ajax")
							.addClass("parse")
							.children()
								.removeClass("ajax parse")
								.css("width", "0%");
						}
						break;
					}

					// Set text
					if (text) container.find(".post_body").text(text_str);
				},
				create_inline_instance: function (data, post_target) {
					// Create and parent
					data.inlined_instance = post_target.create_inline(data.instance);
					// Settings
					var container = data.inlined_instance.container;
					container.addClass("floating");
					// Highlight quote
					(data.backlink ?
						Post.get_html_references(data.inlined_instance.container, '[post="' + data.ref.target_post_id + '"][thread="' + data.ref.target_thread_id + '"][board="' + data.ref.target_board + '"]') :
						Post.get_html_backlinks(data.inlined_instance.container, '[post="' + data.ref.origin.id + '"][thread="' + data.ref.origin.thread.id+ '"][board="' + data.ref.origin.thread.board + '"]')
					).addClass("highlighted");
					// Add
					content.floating_container.append(container);
					// Position
					events.position_floating_post.call(this, container, data);
					// Add inline reference
					data.instance.add_inlined_instance(data.inlined_instance);
				},
				set_inline_floating: function (obj, container, data, floating, instance) {
					if (floating) {
						// Close
						obj.removeClass("open");
						var par = container.parent();

						// Move
						content.floating_container.append(container);
						container.addClass("floating").removeClass("focused");
						events.position_floating_post.call(this, container, data);

						// Remove more inlines
						if (data.inlined_instance != null) {
							data.inlined_instance.remove_inlines();
						}

						// Empty parent?
						if (data.backlink) {
							if (par.contents().length == 0) {
								par.addClass("empty");
							}
						}

						// Highlighting
						if (instance != null) {
							if (settings.post.inlining.highlight_on_hover.if_floating.original) {
								instance.post.instances[0].container.addClass("focused");
							}
							else {
								instance.post.instances[0].container.removeClass("focused");
							}
						}
					}
					else {
						// Find container to place
						var target_container = null;
						if (data.backlink) {
							var par_container = data.instance.container.children(".post");
							var selector = ".post_embedded_backlink_container";
							target_container = par_container.children(selector).add(par_container.children(".post_footer").children(selector));
						}

						if (target_container == null || target_container.length > 0) {
							// Open
							obj.addClass("open");

							// Move
							if (target_container != null) {
								target_container.prepend(container).removeClass("empty");
							}
							else {
								obj.after(container);
							}

							container.removeClass("floating");
							container.css({left:"",top:""});

							// Focus
							if (settings.post.inlining.highlight_on_hover.if_embedded.target) {
								container.addClass("focused");
							}
							if (instance != null) {
								if (settings.post.inlining.highlight_on_hover.if_embedded.original) {
									instance.post.instances[0].container.addClass("focused");
								}
								else {
									instance.post.instances[0].container.removeClass("focused");
								}
							}
						}
					}
				},
				set_thread_expansion: function (data, expand) {
					var thread = data.post.thread;
					thread.set_expanded(expand);

					if (expand) {
						// Update
						data.object.parent().removeClass("expand").addClass("shrink");
					}
					else {
						// Update
						data.object.parent().removeClass("shrink").addClass("expand");
					}
				},
				open_image_preview: function (obj, event_data, timeout) {
					if (event_data.image.width <= 0 || event_data.image.height <= 0) return;

					// Spawn function
					var fcn = function () {
						event_data.open_timer = null;
						if (event_data.preview == null) {
							// Create new
							event_data.preview = event_data.image.create_preview(obj, event_data, events.image_click);
							// Add
							content.floating_container.append(event_data.preview);
						}
					};
					// Timeout
					if (event_data.open_timer !== null) {
						clearTimeout(event_data.open_timer);
					}
					// Time
					if (timeout > 0) {
						event_data.open_timer = setTimeout(fcn, timeout * 1000);
					}
					else {
						fcn();
					}
				},
				check_for_cycle: function (new_post, instance) {
					while (instance != null) {
						if (instance.post == new_post) return instance;
						instance = instance.parent;
					}
					return null;
				},
				check_on_screen: function (container, header) {
					// Get the inner container
					container = container.children(".post");

					var win = $(window);
					var area = header.get_screen_area();
					var c_pos = container.offset();
					var c_width = container.outerWidth();
					var c_height = container.outerHeight();
					area.left += win.scrollLeft();
					area.top += win.scrollTop();

					if (
						(c_pos.left <= area.left + area.width && c_pos.left + c_width >= area.left) &&
						(c_pos.top <= area.top + area.height && c_pos.top + c_height >= area.top)
					) {
						if (
							(c_pos.left >= area.left && c_pos.left + c_width <= area.left + area.width) &&
							(c_pos.top >= area.top && c_pos.top + c_height <= area.top + area.height)
						) {
							return "complete";
						}
						return "partial";
					}
					return "none";
				},

				reference_mouseenter: function (event) {
					if (event.data.inlined_instance == null) {
						var p_t = (event.data.backlink ? event.data.ref.origin : event.data.ref.target);
						if (p_t != null) {
							var cycle_object = null;
							if (settings.post.inlining.allow_cyclic || (cycle_object = events.check_for_cycle(p_t, event.data.instance)) == null) {
								// On screen setting
								var on_screen = events.check_on_screen.call(this, p_t.instances[0].container, content.header);
								on_screen = settings.post.inlining.on_screen[on_screen];
								if (on_screen.inline) {
									// Create
									events.create_inline_instance.call(this, event.data, p_t);
								}
							}

							// Highlighting
							if (cycle_object != null) {
								if (settings.post.inlining.highlight_on_hover.if_floating.target) {
									cycle_object.container.addClass("focused");
								}
							}
							if (settings.post.inlining.highlight_on_hover.if_floating.original) {
								p_t.instances[0].container.addClass("focused");
							}
						}
						else if (!event.data.backlink) {
							if (!event.data.ref.target_dead && event.data.ref.target_thread_id !== null) {
								// Message
								if (event.data.temporary == null) {
									// Create
									event.data.temporary = event.data.ref.origin.create_inline_message();
									// Settings
									var container = event.data.temporary;
									container.addClass("floating");
									// Add
									content.floating_container.append(container);
									// Set text
									container.find(".post_subject").text("Loading Post");
									// Update status
									events.update_loader_status.call(this, container, event.data.loader, true, ".post_header_progress");
									// Position
									events.position_floating_post.call(this, container, event.data);
								}
								else {
									// Highlight if embedded
									if (!event.data.temporary.hasClass("floating")) {
										if (settings.post.inlining.highlight_on_hover.if_embedded.target) {
											event.data.temporary.addClass("focused");
										}
									}
								}
								// Run the loader (if it isn't already)
								event.data.loader.run();
							}
						}
					}
					else {
						// Highlighting
						if (!event.data.inlined_instance.container.hasClass("floating")) {
							if (settings.post.inlining.highlight_on_hover.if_embedded.target) {
								event.data.inlined_instance.container.addClass("focused");
							}
							if (settings.post.inlining.highlight_on_hover.if_embedded.original) {
								event.data.inlined_instance.post.instances[0].container.addClass("focused");
							}
						}
					}
				},
				reference_mouseleave: function (event) {
					// Unhighlight
					var p_t = (event.data.backlink ? event.data.ref.origin : event.data.ref.target);
					if (p_t != null) {
						var cycle_object = events.check_for_cycle(p_t, event.data.instance);
						if (cycle_object != null) {
							cycle_object.container.removeClass("focused");
						}
						p_t.instances[0].container.removeClass("focused");
					}
					if (event.data.inlined_instance != null) {
						// Unhighlight
						event.data.inlined_instance.container.removeClass("focused");

						// Remove
						if (event.data.inlined_instance.container.hasClass("floating")) {
							event.data.inlined_instance.destroy();
							event.data.inlined_instance = null;
						}
					}
					else if (event.data.temporary != null) {
						// Unhighlight
						event.data.temporary.removeClass("focused");

						// Remove
						if (event.data.temporary.hasClass("floating")) {
							event.data.temporary.remove();
							event.data.temporary = null;
						}
					}
				},
				reference_mousemove: function (event) {
				},
				reference_click: function (event) {
					if (event.which != 1) return true;

					// On screen check
					var p_t = (event.data.backlink ? event.data.ref.origin : event.data.ref.target);
					if (p_t != null) {
						var on_screen = events.check_on_screen.call(this, p_t.instances[0].container, content.header);
						on_screen = settings.post.inlining.on_screen[on_screen];
						if (!on_screen.embed) {
							// Scroll to
							if (on_screen.scroll) {
								event.data.post.scroll_to(p_t.instances[0].container);
							}
							if (on_screen.highlight) {
								location.go_to(info.create_url.to_post(p_t.thread.board, p_t.thread.id, p_t.id));
							}
							// Done, don't inline
							return false;
						}
					}

					// Inline
					var container = (event.data.inlined_instance != null ? event.data.inlined_instance.container : event.data.temporary);
					if (container != null) {
						// Inline
						var obj = $(this);

						events.set_inline_floating.call(this, obj, container, event.data, obj.hasClass("open"), event.data.inlined_instance);
					}

					return false;
				},
				reference_update: function (event) {
					// Reference has been updated
					if (event.data.temporary != null) {
						var floating = event.data.temporary.hasClass("floating");
						var focused = event.data.temporary.hasClass("focused");
						// Remove
						event.data.temporary.remove();
						event.data.temporary = null;
						// Add
						var p_t = (event.data.backlink ? event.data.ref.origin : event.data.ref.target);
						if (p_t != null) {
							// Create
							events.create_inline_instance.call(this, event.data, p_t);

							// Inline
							if (!floating) {
								var container = event.data.inlined_instance.container;
								var obj = $(this);
								events.set_inline_floating.call(this, obj, container, event.data, false, event.data.inlined_instance);
							}

							if (focused) {
								event.data.inlined_instance.container.addClass("focused");
							}
						}
					}
				},

				reference_loader_progress: function (event, data) {
					// Progress
					if (data.temporary != null) {
						data.temporary.find(".post_header_progress_inner")
						.css("width", (event.ratio * 100).toFixed(2) + "%");
					}
				},
				reference_loader_status_change: function (event, data) {
					// Change status
					if (data.temporary != null) {
						events.update_loader_status.call(this, data.temporary, this, true, ".post_header_progress");
					}
				},

				notification_loader_progress: function (event, data) {
					// Update percent
					data.object.find(".post_notification_progress_inner")
					.css("width", (event.ratio * 100).toFixed(2) + "%");
				},
				notification_loader_status_change: function (event, data) {
					// Change status
					events.update_loader_status.call(this, data.object, this, false, ".post_notification_progress");
				},
				notification_loader_complete: function (event, data) {
					if (!data.complete) {
						// Expand thread
						var p = data.object.parent();
						if (p.hasClass("active")) {
							// Expand
							p.removeClass("active").addClass("loaded");
							events.set_thread_expansion.call(data.object, data, true);
						}
						else {
							p.addClass("loaded");
							events.set_thread_expansion.call(data.object, data, false);
						}
					}
				},
				notification_loader_post_load: function (event, data) {
					if (!data.complete && event.post == data.post) {
						// Expand
						var p = data.object.parent();
						if (p.hasClass("active")) {
							data.instance.container.children(".post").children(".post_body").addClass("full");
							p.addClass("shrink loaded").removeClass("active");
						}
						else {
							p.addClass("expand loaded");
						}
					}
				},

				notification_truncated_message_click: function (event) {
					if (event.which != 1) return true;

					var par = $(this).parent();

					if (par.hasClass("loaded")) {
						// Toggle
						var body = event.data.instance.container.children(".post").children(".post_body");
						if (body.hasClass("full")) {
							// Shrink
							var h1 = body.next().outerHeight();
							// Change display
							body.removeClass("full");
							par.removeClass("shrink").addClass("expand");
							// Scroll up
							var h2 = body.outerHeight();
							var top = $(document).scrollTop();
							$(document).scrollTop(Math.max(0, top - (h1 - h2)));
						}
						else {
							// Expand
							body.addClass("full");
							par.removeClass("expand").addClass("shrink");
						}
					}
					else {
						// Active
						if (par.hasClass("active")) {
							par.removeClass("active");
						}
						else {
							par.addClass("active");
						}
						// Run the loader
						event.data.loader.run();
					}

					return false;
				},
				notification_omitted_posts_click: function (event) {
					if (event.which != 1) return true;

					var par = $(this).parent();

					if (par.hasClass("loaded")) {
						// Expand/shrink
						events.set_thread_expansion.call(this, event.data, par.hasClass("expand"));
					}
					else {
						// Active
						if (par.hasClass("active")) {
							par.removeClass("active");
						}
						else {
							par.addClass("active");
						}
						// Run the loader
						event.data.loader.run();
					}

					return false;
				},

				header_identity_click: function (event) {
					if (event.which != 1) return true;

					return false;
				},
				header_number_click: function (event) {
					if (event.which != 1) return true;

					if ($(this).hasClass("post_no")) {
						// Post "No."
						if (info.index) return true;

						var post_id = event.data.post.id.toString();
						if (location.fragment == post_id) {
							location.go_to(
								info.create_url.to_thread(event.data.post.thread.board, event.data.post.thread.id)
							);
						}
						else {
							location.go_to(
								info.create_url.to_post(event.data.post.thread.board, event.data.post.thread.id, event.data.post.id)
							);
						}
					}
					else {
						// Post #
						content.qr.quote(event.data.post);
					}
					return false;
				},
				header_settings_click: function (event) {
					if (event.which != 1) return true;

					// Open settings menu
					if (event.data.instance.settings_menu == null) {
						// Form options
						var options = [];
						var event_data = event.data;

						if (!event.data.post.deleted) {
							options.push({
								text: "Report",
								on: {
									click: function (event) {
										if (event.which != 1) return true;

										event.data.option.menu.close();

										content.report_post(event_data.post, "", false);

										return false;
									}
								}
							});

							options.push({
								text: "Delete",
								on: {
									click: function (event) {
										if (event.which != 1) return true;

										event.data.option.menu.close();

										content.delete_post(event_data.post, false);

										return false;
									}
								}
							});
							if (event.data.post.image != null && event.data.post.image.good() && !event.data.post.image.deleted) {
								options.push({
									text: "Delete image",
									on: {
										click: function (event) {
											if (event.which != 1) return true;

											event.data.option.menu.close();

											content.delete_post_image(event_data.post, false);

											return false;
										}
									}
								});
							}
						}
						if (content.posted_by_me(event.data.post)) {
							options.push({
								text: "Unmark as (you)",
								on: {
									click: function (event) {
										if (event.which != 1) return true;

										event.data.option.menu.close();

										// Unmark
										content.mark_post_as_me_ask(event_data.post, false, false);

										return false;
									}
								}
							});
						}

						// Create
						if (options.length > 0) {
							event.data.instance.settings_menu = new xch.Menu($(this), {
								close_on_mouseleave: false,
								fit_width_min: true,
								keep_on_screen: true,
								fixed_z: null,
								open: "down",
								on: {
									close: {
										callback_data: event.data.instance,
										callback: events.on_settings_menu_close
									}
								},
								options: options
							});
						}
					}
					else {
						event.data.instance.settings_menu.close();
					}
				},

				on_settings_menu_close: function (event, instance) {
					instance.settings_menu = null;
				},

				image_click: function (event) {
					if (event.which != 1) return true;

					// Non-deleted image
					var ev_data = event.data;
					var is_preview = ("preview_data" in ev_data);

					var obj = is_preview ? ev_data.obj : $(this);
					if (obj.attr("href") !== undefined) {
						// What to do on click
						if (is_preview) ev_data = ev_data.preview_data;
						var opt = settings.image_preview.click;
						opt = (is_preview ? opt.if_preview : (ev_data.preview == null ? opt.if_closed : opt.if_open));
						var par = obj.parent();
						if (par.hasClass("embed")) opt = "embed";
						if (ev_data.image.width == 0 || ev_data.image.height == 0) opt = "embed";

						// Switch
						if (opt == "toggle") {
							if (ev_data.preview == null) {
								// Open
								events.open_image_preview.call(this, obj, ev_data, 0);
							}
							else {
								// Close
								ev_data.image.close_preview(ev_data);
							}
						}
						else if (opt == "tab") {
							// Let it open in a new tab
							return true;
						}
						else if (opt == "embed") {
							// Embed toggle
							var embed = !par.hasClass("embed");
							ev_data.post.set_image_embedded(embed, ev_data.instance, par);
							// Close
							if (embed) ev_data.image.destroy_preview(ev_data);
						}
						// Else, do nothing

						// Cancel event
						return false;
					}
				},
				image_mouseenter: function (event) {
					// Non-deleted image
					var obj = $(this);
					if (obj.attr("href") !== undefined && !obj.parent().hasClass("embed")) {
						if (event.data.preview == null) {
							if (event.data.open_timer === null && event.data.image.previewable()) {
								// Time
								var t = settings.image_preview;
								t = (event.data.image.spoiler ? t.open_timeout_spoiler : t.open_timeout);
								// Spawn
								events.open_image_preview.call(this, obj, event.data, t);
							}
						}
						else {
							// Cancel closing
							event.data.image.close_preview_cancel(event.data);
						}
					}
				},
				image_mouseleave: function (event) {
					// Non-deleted image
					if ($(this).attr("href") !== undefined) {
						// Remove
						if (event.data.preview != null) {
							event.data.image.close_preview(event.data);
						}
						else if (event.data.open_timer !== null) {
							clearTimeout(event.data.open_timer);
							event.data.open_timer = null;
						}
					}
				},
				image_window_scroll: function (event) {
					if (event.data.preview != null) {
						// Position
						event.data.image.position_preview(event.data.object, event.data.preview, event.data, false);
					}
				},
				image_window_resize: function (event) {
					if (event.data.preview != null) {
						// Position
						event.data.image.position_preview(event.data.object, event.data.preview, event.data, false);
					}
				}

			};

			var this_private = {

				backlink_insert_sort_function: function (existing_element, inserting_element, ref) {
					// Sorting function
					var b = existing_element.attr("board");
					var p = parseInt(existing_element.attr("post"));
					return (b == ref.target_board ? p < ref.origin.id : (b == ref.origin.thread.board ? p < ref.origin.id : b < ref.origin.thread.board));
				},

				post_insert_sort_function: function (existing_element, inserting_element) {
					return ((parseInt(inserting_element.attr("post_id") || "0") || 0) > (parseInt(existing_element.attr("post_id") || "0") || 0));
				},

				parse_immediate_new: function (data) {
					var self = this;

					// Create instance
					var instance = new Post.Instance(this, data.html);
					this.instances[0] = instance;
					var html = data.html;

					// Intro
					var intro = html.find(".intro");


					// Find image
					var img = null, img_data;
					if (this.op) {
						img_data = html.parent().children(".fileinfo");
						img = html.prev();
						if (img.prop("tagName") != "IMG") {
							img = img.children("img");
							if (img.length == 0) img = null;
						}
					}
					else {
						img_data = html.find(".fileinfo");
						if (img_data.length == 0) {
							if ((img = intro.next()).prop("tagName") != "IMG") img = null;
						}
						else {
							if ((img = img_data.next().children("img")).prop("tagName") != "IMG") img = null;
						}
					}

					// Image data
					if (img_data.length > 0 || img != null) {
						// Has image
						this.image = new xch.Image();
						this.image.parse((img_data.length > 0 ? $(img_data[0]) : null), img, data.context.info);
					}


					// Time
					if ((o = intro.find("time[datetime]")).length > 0) {
						var m = /([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+)/i.exec(o.attr("datetime") || "");
						if (m) {
							this.timestamp = (new Date(
								parseInt(m[1]),
								parseInt(m[2]) - 1,
								parseInt(m[3]),
								parseInt(m[4]),
								parseInt(m[5]),
								parseInt(m[6])
							)).getTime();
						}
					}


					// Stylizing data
					var stylize_data = {
						subject: null,
						name: null,
						email: null,
						tripcode: null,
						capcode: null,
						comment: null
					};

					// Subject
					var o;
					if ((o = intro.find(".subject")).length > 0) {
						stylize_data.subject = (o.text() || "").trim();
					}
					// Name
					if ((o = intro.find(".name")).length > 0) {
						stylize_data.name = (o.text() || "").trim();
					}
					// Email
					if ((o = intro.find(".email")).length > 0) {
						stylize_data.email = (o.attr("href") || "").substr(7);
					}
					// Tripcode
					if ((o = intro.find(".trip")).length > 0) {
						stylize_data.tripcode = (o.text() || "").trim();
					}
					// Tripcode
					if ((o = intro.find(".capcode")).length > 0) {
						stylize_data.capcode = (o.text() || "").trim().replace(/^\s*#+\s*/, "");
					}

					// Comment
					stylize_data.comment = style.set_class(html.find(".body"));
					this.comment_truncated = (stylize_data.comment.find(".toolong").length > 0);

					// Stylize
					if (data.context.stylize) {
						this_private.stylize.call(this, this.instances[0], (data.context.html_target == null), stylize_data);
						this.prepare(this.instances[0]);
					}

					// Incomplete reference checking
					content.check_for_incomplete_references(this);

					// Loaded
					this.thread.post_loaded(this, data.context, data.thread_context, data.post_context, false);

				},
				parse_immediate_old: function (data) {
					var changed = false;
					var update_data = {
						comment_fully_loaded: false,
						image_deleted: false
					};
					var html = data.html;

					// Update comment (if truncated)
					if (this.comment_truncated && html.find(".body").find(".toolong").length == 0) {
						// Get the remaining comment
						var com = style.set_class(html.find(".body"), "post_body_full"), new_com;
						this_private.stylize_comment.call(this, com);
						// Insert
						for (var i = 0, j = this.instances.length; i < j; ++i) {
							// Prepare
							new_com = (i == 0 ? com : com.clone());
							this.prepare_comment(this.instances[i], new_com);
							// Append
							this.instances[i].container.children(".post").children(".post_body").after(new_com);
						}

						// Not truncated
						this.comment_truncated = false;
						changed = true;
						update_data.comment_fully_loaded = true;
					}

					// Intro
					var intro = html.find(".intro");

					// Find image
					var img = null, img_data;
					if (this.op) {
						img_data = html.parent().children(".fileinfo");
						img = html.prev();
						if (img.prop("tagName") != "IMG") {
							img = img.children("img");
							if (img.length == 0) img = null;
						}
					}
					else {
						img_data = html.find(".fileinfo");
						if (img_data.length == 0) {
							if ((img = intro.next()).prop("tagName") != "IMG") img = null;
						}
						else {
							if ((img = img_data.next().children("img")).prop("tagName") != "IMG") img = null;
						}
					}

					// Image data
					var image = null;
					if (img_data.length > 0 || img != null) {
						// Has image
						image = new xch.Image();
						image.parse((img_data.length > 0 ? $(img_data[0]) : null), img, data.context.info);
					}

					// Check if image is deleted
					if ((this.image != null && !this.image.deleted) && (image == null || image.deleted)) {
						// Image deleted
						this.mark_as_dead_image(true);

						// Changed
						changed = true;
						update_data.image_deleted = true;
					}

					// Loaded
					this.thread.post_loaded(this, data.context, data.thread_context, data.post_context, changed, update_data);
				},

				stylize: function (instance, replace_old_style, stylize_data) {
					// New container
					var outer = style.e("div", "post_container " + (this.op ? "op" : "reply"));
					var inner = style.e("div", "post");
					outer.attr("post_id", this.id);

					//{ Header
						var header = style.e("div", "post_header")

						// Subject
						if (stylize_data.subject !== null) {
							var subject_container = style.e("span", "post_subject").text(stylize_data.subject);
							header.append(subject_container);
						}

						// Name
						var has_name = false;
						var name_container = style.e("a", "post_identity");
						if (stylize_data.name !== null) {
							name_container.append(
								style.e("span", "post_identity_name").text(stylize_data.name)
							);
							has_name = true;
						}
						if (stylize_data.tripcode !== null) {
							name_container.append(
								style.e("span", "post_identity_tripcode").text(stylize_data.tripcode)
							);
							has_name = true;
						}
						if (stylize_data.capcode !== null) {
							name_container.append(
								style.e("span", "post_identity_capcode_indicator").text(style.post.header.identity.capcode.indicator)
							)
							.append(
								style.e("span", "post_identity_capcode").text(stylize_data.capcode)
							);

							var cc = stylize_data.capcode.toLowerCase();
							if (cc.indexOf("admin") >= 0) name_container.addClass("admin");
							else if (cc.indexOf("mod") >= 0) name_container.addClass("mod");

							has_name = true;
						}
						if (has_name) {
							if (stylize_data.email !== null) {
								name_container.attr("href", "mailto:" + stylize_data.email);
							}
							header.append(name_container);
						}

						// More header stuff
						header.append(
							style.e("span", "post_time")
							.text(xch.date.format(this.timestamp + settings.post.time.offset, settings.post.time.format))
						)
						.append(
							style.e("a", "post_no")
							.attr("href", info.create_url.to_post(this.thread.board, this.thread.id, this.id))
							.text(style.post.header.number.indicator)
						)
						.append(
							style.e("a", "post_number no_spacing")
							.attr("href", info.create_url.to_post(this.thread.board, this.thread.id, this.id))
							.text(this.id)
						);

						// Icons
						if (this.op) {
							// Sticky
							if (this.thread.sticky) {
								header.append(
									style.e("span", "post_icon sticky")
									.attr("title", "Sticky")
								);
							}

							// Locked
							if (this.thread.locked) {
								header.append(
									style.e("span", "post_icon locked")
									.attr("title", "Locked")
								);
							}

							// Reply
							if (info.index) {
								header.append(
									style.e("span", "post_option reply_to no_inline")
									.append(
										style.e("a", "post_option_text")
										.attr("href", info.create_url.to_thread(this.thread.board, this.thread.id))
									)
								);
							}
						}

						// Backlinks
						if (!settings.post.footer_backlinks) {
							header.append(
								style.e("span", "post_backlinks empty")
							);
						}

						// Options
						header.append(
							style.e("span", "post_settings")
						);

					//}

					// File info
					var file_info = null, file_image = null, file_extras = null;
					if (this.image != null) {
						if (!this.image.deleted) {
							var file_info_string = this.image.filesize_label[0] + " " + this.image.filesize_label[1];
							if (this.image.width > 0 && this.image.height > 0) {
								file_info_string += ", " + this.image.width + style.symbols.mult + this.image.height;
							}

							file_info = style.e("div", "post_file_info")
							.append(
								style.e("a", "post_file_info_file")
								.text(this.image.filename_original)
								.attr("href", this.image.url)
								.attr("target", "_blank")
							)
							.append(
								style.e("span", "post_file_info_attributes")
								.append(
									style.e("span")
									.text(file_info_string)
								)
							)
							.append(
								(file_extras = style.e("span", "post_file_info_extra_links"))
							);
						}

						var img;
						file_image = style.e("div", "post_image_container").append(
							style.e("a", "post_image_link")
							.attr("href", this.image.url)
							.attr("target", "_blank")
							.append(
								(img = style.e("img", "post_image"))
								.attr("src", this.image.thumbnail)
								.attr("title", "")
								.attr("alt", "")
							)
						);
						this.image.stylize_size(img, this.op);
					}

					// Comment
					stylize_data.comment.find(".toolong").remove();
					stylize_data.comment.addClass("post_body");
					this_private.stylize_comment.call(this, stylize_data.comment);

					// Notifications
					var notifications = style.e("div", "post_notifications empty");
					if (this.op && (this.thread.posts_omitted > 0 || this.thread.images_omitted > 0)) {
						notifications.removeClass("empty").append(
							style.e("div", "post_notification omitted")
							.append(
								style.e("a", "post_notification_message")
								.attr("href", info.create_url.to_thread(this.thread.board, this.thread.id))
								.append(
									style.e("div", "post_notification_progress").html(
										style.e("div", "post_notification_progress_inner")
									)
								)
								.append(
									style.e("span", "post_notification_text")
									.attr("omitted_value", this.thread.posts_omitted + " repl" + (this.thread.posts_omitted == 1 ? "y" : "ies") + (this.thread.images_omitted > 0 ? (" and " + this.thread.images_omitted + " image" + (this.thread.images_omitted == 1 ? "" : "s")) : ""))
								)
							)
						);
					}
					if (this.comment_truncated) {
						notifications.removeClass("empty").append(
							style.e("div", "post_notification truncated")
							.append(
								style.e("a", "post_notification_message")
								.attr("href", info.create_url.to_post(this.thread.board, this.thread.id, this.id))
								.append(
									style.e("div", "post_notification_progress").html(
										style.e("div", "post_notification_progress_inner")
									)
								)
								.append(style.e("span", "post_notification_text"))
							)
						);
					}


					// Footer
					var footer = style.e("div", "post_footer hidden empty");
					if (settings.post.footer_backlinks) {
						footer.append(
							style.e("span", "post_backlinks")
						)
						.append(
							style.e("div", "post_embedded_backlink_container empty")
						);

						footer.removeClass("hidden");
					}

					// Source info
					if (file_extras != null) {
						content.append_sourcing_info(file_extras, this);
					}

					// Add contents
					if (!this.op) inner.append(header);
					if (file_info != null) inner.append(file_info);
					if (file_image != null) inner.append(file_image);
					if (this.op) inner.append(header);

					if (!settings.post.footer_backlinks) {
						// Top backlinks
						var top_backlink_container = style.e("div", "post_embedded_backlink_container empty");
						header.after(top_backlink_container);
					}

					inner
					.append(stylize_data.comment)
					.append(style.e("div", "post_body_after"))
					.append(notifications)
					.append(footer);
					outer.append(inner);

					// Replace
					if (replace_old_style) {
						// Remove old stuff
						var p, par, cont;
						if (this.op) {
							if ((p = instance.container.prev()).length > 0 && (p.prop("tagName") == "A" || p.prop("tagName") == "IMG")) {
								p.remove();
							}
							if ((p = instance.container.prev()).length > 0 && p.hasClass("fileinfo")) {
								p.remove();
							}
						}
						else {
							// Remove separator after
							if ((p = instance.container.prev()).length > 0 && p.prop("tagName") == "BR") {
								p.remove();
							}
						}

						// Replace and remove old
						cont = instance.container[0];
						par = cont.parentNode;
						if (par != null) {
							var ns = cont.nextSibling;

							par.removeChild(cont);

							if (ns == null) {
								par.appendChild(outer[0]);
							}
							else {
								par.insertBefore(outer[0], ns);
							}
						}
					}
					// New
					instance.container = outer;
				},
				stylize_comment: function (comment) {
					var self = this;

					// Greentext
					var quotes = comment.find(".quote");
					for (var i = 0; i < quotes.length; ++i) {
						// Dead links
						var obj = $(quotes[i]);
						var c = obj.contents();
						if (c.length == 1) {
							c = /^>>(?:>\/(\w+)\/)?([0-9]+)$/i.exec(c.text());
							if (c) {
								// Set
								var board = c[1] || this.thread.board;
								var post_id = parseInt(c[2]);
								var thread_id = null;

								// Add reference
								var rr, ref = this.add_reference(board, thread_id, post_id);

								// Make a resurrected quote
								obj.after(
									(rr = style.e("a", "post_reference resurrected"))
									.attr("board", board)
									.attr("post", post_id)
									.attr("href", info.create_url.to_post(board, this.thread.id, post_id))
								);
								style.format_reference_style(rr, ref);

								// Remove dead
								obj.remove();
								return;
							}
						}

						// Add class
						style.set_class(obj, "quote");
					}

					// Spoilers
					style.set_class(comment.find(".spoiler"), "spoiler");

					// Links
					var links = comment.find("a");
					var re = /\/\/boards\.38chan\.net\/(\w+)\/res\/([0-9]+)\.html#([0-9]+)/i;
					for (var i = 0; i < links.length; ++i) {
						var obj = $(links[i]);
						var m = re.exec(obj.attr("href") || "");
						if (m) {
							// Reference
							var board = m[1];
							var thread_id = parseInt(m[2]);
							var post_id = parseInt(m[3]);
							// Update element
							style.set_class(obj, "post_reference")
							.removeAttr("onclick")
							.off("click")
							.attr("board", board)
							.attr("thread", thread_id)
							.attr("post", post_id)
							.attr("href", info.create_url.to_post(board, thread_id, post_id));
							// Add reference
							var ref = this.add_reference(board, thread_id, post_id);
							// Re-format
							style.format_reference_style(obj, ref);
						}
						else {
							obj.addClass("post_link");
						}
					}
				},
				stylize_reference: function (ref) {
					if (ref.target != null && ref.target != ref.origin) {
						// Update reply count
						for (var i = 0, j = ref.target.instances.length; i < j; ++i) {
							// Find post
							var c = ref.target.instances[i].container.children(".post");

							// Add backlink
							var selector = ".post_backlinks";
							var footer = c.children(".post_footer:not(.hidden)");
							var backlink_container = (footer.length > 0 ? footer : c.children(".post_header")).find(selector);
							var backlink;
							// Backlink
							(backlink = style.e("a", "post_backlink"))
							.attr("board", ref.origin.thread.board)
							.attr("thread", ref.origin.thread.id)
							.attr("post", ref.origin.id)
							.attr("href", info.create_url.to_post(ref.origin.thread.board, ref.origin.thread.id, ref.origin.id));
							style.format_backlink_style(backlink, ref);
							// Insert
							xch.insert_dom_element_sorted(backlink_container, backlink, this_private.backlink_insert_sort_function, ref);
							(footer.length > 0 ? footer : backlink_container).removeClass("empty");

							// Reference links
							ref.target.prepare_backlink(backlink, ref.target.instances[i]);
						}
					}
				},

				mark_as_dead_update_reference_function: function (ref, target_post) {
					// Dead
					ref.target_dead = true;
				},

				get_comment_plaintext_dom_filter: function (obj, parent) {
					return (!obj.hasClass("inline post_container"));
				},

				on_embedded_post_image_load: function (event) {
					event.data.obj.addClass("loaded");
				},

				prepare_notification_truncated: function (obj, instance) {
					var data = { post: this, instance: instance, loader: null, object: obj };
					data.loader = xch.Loader.get_thread(
						this.thread.board,
						this.thread.id,
						null,
						{
							progress: function (event) { return events.notification_loader_progress.call(this, event, data); },
							post_load: function (event) { return events.notification_loader_post_load.call(this, event, data); },
							status_change: function (event) { return events.notification_loader_status_change.call(this, event, data); }
						}
					);
					instance.bind_html_event(
						obj,
						"click", data, events.notification_truncated_message_click, false
					);
				},
				prepare_notification_omitted: function (obj, instance) {
					var data = { post: this, instance: instance, loader: null, object: obj };
					data.loader = xch.Loader.get_thread(
						this.thread.board,
						this.thread.id,
						null,
						{
							progress: function (event) { return events.notification_loader_progress.call(this, event, data); },
							complete: function (event) { return events.notification_loader_complete.call(this, event, data); },
							status_change: function (event) { return events.notification_loader_status_change.call(this, event, data); }
						}
					);
					instance.bind_html_event(
						obj,
						"click", data, events.notification_omitted_posts_click, false
					);
				}

			};

			Post.prototype = {

				constructor: Post,

				init: function (html, context) {
					// OP and id
					this.op = html.hasClass("op");
					this.id = parseInt((html.find(".intro .post_no:nth-of-type(2)").html() || "").trim()) || 0;
				},
				parse: function (html, context, thread_context, post_context) {
					// Queue
					context.post_queue.queue(this, {
						html: html,
						context: context,
						thread_context: thread_context,
						post_context: post_context
					});
				},
				parse_immediate: function (data) {
					if (data.post_context.is_new) {
						this_private.parse_immediate_new.call(this, data);
					}
					else {
						this_private.parse_immediate_old.call(this, data);
					}
				},
				add_reference: function (board, thread_id, post_id) {
					var ref;
					if ((ref = Post.Reference.find(this, board, thread_id, post_id)) == null) {
						// New
						ref = new Post.Reference(this, board, thread_id, post_id);

						// Stylize
						this_private.stylize_reference.call(this, ref);
					}
					return ref;
				},
				update_reference: function (ref, update_function, update_function_data) {
					// Selector
					var selector = ".post_reference";
					if (ref.target_board !== null) selector += '[board="' + ref.target_board + '"]';
					if (ref.target_thread_id !== null) selector += '[thread="' + ref.target_thread_id + '"]';
					if (ref.target_post_id !== null) selector += '[post="' + ref.target_post_id + '"]';

					// Update
					if (update_function) update_function.call(this, ref, update_function_data, this);

					// Fix origin
					for (var i = 0, j = ref.origin.instances.length; i < j; ++i) {
						// Find post comment
						var c = ref.origin.instances[i].container.children(".post").children(".post_body");
						// Check each
						var h_refs = c.find(selector);
						for (var k = 0; k < h_refs.length; ++k) {
							// Must have correct parent
							var obj = $(h_refs[k]);
							if (obj.parents(".post_body")[0] == c[0]) {
								// Fix reference
								ref.origin.update_reference_html(ref, obj, ref.origin.instances[i]);
							}
						}
					}
				},
				update_reference_style: function (ref) {
					this_private.stylize_reference.call(this, ref);
				},
				update_reference_html: function (ref, obj, instance) {
					// Change attributes
					var okay = true;
					if (ref.target_board !== null) {
						obj.attr("board", ref.target_board);
					}
					else {
						okay = false;
						obj.removeAttr("board");
					}
					if (ref.target_thread_id !== null) {
						obj.attr("thread", ref.target_thread_id);
					}
					else {
						okay = false;
						obj.removeAttr("thread");
					}
					if (ref.target_post_id !== null) {
						obj.attr("post", ref.target_post_id);
					}
					else {
						okay = false;
						obj.removeAttr("post");
					}

					// Change text and link
					if (okay) {
						obj.attr("href", info.create_url.to_post(ref.target_board, ref.target_thread_id, ref.target_post_id));
					}
					else {
						obj.removeAttr("href");
					}
					style.format_reference_style(obj, ref);

					// Prepare
					if (instance) this.prepare_reference(obj, instance);
				},
				update_backlink: function (ref, update_function, update_function_data) {
					// Selector
					var selector = ".post_backlink";
					selector += '[board="' + ref.origin.thread.board + '"]';
					selector += '[thread="' + ref.origin.thread.id + '"]';
					selector += '[post="' + ref.origin.id + '"]';

					// Update
					if (update_function) update_function.call(this, ref, update_function_data, this);
					if (ref.target == null) return;

					// Fix origin
					for (var i = 0, j = ref.target.instances.length; i < j; ++i) {
						// Find post
						var c = ref.target.instances[i].container.children(".post");

						// Add backlink
						var footer = c.children(".post_footer:not(.hidden)");
						var header = c.children(".post_header");
						var backlink_container = header.add(footer).find(".post_backlinks");

						// Check each
						var backlinks = backlink_container.find(selector);
						for (var k = 0; k < backlinks.length; ++k) {
							// Check
							var obj = $(backlinks[i]);
							obj
							.attr("board", ref.origin.thread.board)
							.attr("thread", ref.origin.thread.id)
							.attr("post", ref.origin.id)
							.attr("href", info.create_url.to_post(ref.origin.thread.board, ref.origin.thread.id, ref.origin.id));
							style.format_backlink_style(obj, ref);
						}
					}
				},

				get_reference: function (board, thread_id, post_id) {
					for (var i = 0, ref; i < this.references.length; ++i) {
						ref = this.references[i];
						if (ref.target_board == board && ref.target_thread_id === thread_id && ref.target_post_id == post_id) return ref;
					}
					return null;
				},
				get_backlink: function (board, thread_id, post_id) {
					for (var i = 0, post; i < this.replies.length; ++i) {
						post = this.replies[i].origin;
						if (post.thread.board == board && post.thread.id === thread_id && post.id == post_id) return this.replies[i];
					}
					return null;
				},

				prepare: function (instance) {
					var obj, data;

					// Comment
					this.prepare_comment(instance, null);
					var p_container = instance.container.children(".post");
					// Backlinks
					var backlinks = instance.container.find(".post_backlink");
					for (var i = 0; i < backlinks.length; ++i) {
						// Prepare
						this.prepare_backlink($(backlinks[i]), instance);
					}

					if (this.image != null) {
						// Data
						obj = instance.container.find(".post_image_link");
						data = {
							post: this,
							instance: instance,
							image: this.image,
							preview: null,
							object: obj,
							close_timer: null,
							zoom: 1,
							mouse_last: {x:0,y:0},
							bounding: null,
							open_timer: null
						};

						// Image
						instance.bind_html_event(
							obj,
							"click", data, events.image_click, false
						);
						instance.bind_html_event(
							obj,
							"mouseenter", data, events.image_mouseenter, false
						);
						instance.bind_html_event(
							obj,
							"mouseleave", data, events.image_mouseleave, false
						);
						$(window)
						.on("scroll", data, events.image_window_scroll)
						.on("resize", data, events.image_window_resize);

						// Sourcing
						var ex = p_container.children(".post_file_info").children(".post_file_info_extra_links");
						if (ex.length > 0) {
							content.prepare_sourcing_info(ex, this, instance);
						}
					}

					// Data
					data = { post: this, instance: instance };

					// Header
					instance.bind_html_event(
						instance.container.find(".post_identity"),
						"click", data, events.header_identity_click, false
					);
					instance.bind_html_event(
						instance.container.find(".post_no,.post_number"),
						"click", data, events.header_number_click, false
					);
					instance.bind_html_event(
						instance.container.find(".post_settings"),
						"click", data, events.header_settings_click, false
					);

					// Notifications
					obj = instance.container.find(".post_notification.truncated>.post_notification_message");
					if (obj.length > 0) {
						this_private.prepare_notification_truncated.call(this, obj, instance);
					}
					obj = instance.container.find(".post_notification.omitted>.post_notification_message");
					if (obj.length > 0) {
						this_private.prepare_notification_omitted.call(this, obj, instance);
					}

					// Trigger prepare
					api.signal("post_prepare", {
						post: this,
						post_instance: instance
					});
				},

				prepare_comment: function (instance, html) {
					// Find html
					if (html == null) {
						html = instance.container.children(".post").children(".post_body,.post_body_full");
					}

					// Reference links
					var refs = html.find(".post_reference");
					for (var i = 0; i < refs.length; ++i) {
						this.prepare_reference($(refs[i]), instance);
					}
				},
				prepare_reference: function (obj, instance) {
					// Data
					var e = instance.get_html_event(obj[0], "mouseenter");
					if (e !== null) {
						// Update event
						events.reference_update.call(obj[0], { data: e.data });
					}
					else {
						// New
						var data = {
							post: this,
							backlink: false,
							instance: instance,
							inlined_instance: null,
							ref: null,
							loader: null,
							temporary: null
						};
						// Get reference
						var b = obj.attr("board");
						var t = obj.attr("thread");
						var p = parseInt(obj.attr("post"));
						t = (t === undefined ? null : parseInt(t));
						data.ref = this.get_reference(b, t, p);
						/*!debug!*/assert(data.ref !== null, "Bad reference to " + b + "/" + t + "/" + p);
						// Get loader
						if (!data.ref.target_dead && data.ref.target_thread_id !== null) {
							data.loader = xch.Loader.get_thread(
								data.ref.target_board,
								data.ref.target_thread_id,
								null,
								{
									progress: function (event) { return events.reference_loader_progress.call(this, event, data); },
									status_change: function (event) { return events.reference_loader_status_change.call(this, event, data); }
								}
							);
						}

						// Events
						instance.unbind_html_event(obj, "mouseenter");
						instance.unbind_html_event(obj, "mouseleave");
						instance.unbind_html_event(obj, "mousemove");
						instance.unbind_html_event(obj, "click");
						instance.bind_html_event(obj, "mouseenter", data, events.reference_mouseenter, true);
						instance.bind_html_event(obj, "mouseleave", data, events.reference_mouseleave, true);
						instance.bind_html_event(obj, "mousemove", data, events.reference_mousemove, true);
						instance.bind_html_event(obj, "click", data, events.reference_click, true);
					}
				},
				prepare_backlink: function (obj, instance) {
					// Data
					var e = instance.get_html_event(obj[0], "mouseenter");
					if (e !== null) {
						// Update event
						events.reference_update.call(obj[0], { data: e.data });
					}
					else {
						var data = {
							post: this,
							backlink: true,
							instance: instance,
							inlined_instance: null,
							ref: null,
							loader: null,
							temporary: null
						};
						// Get reference
						var b = obj.attr("board");
						var t = obj.attr("thread");
						var p = parseInt(obj.attr("post"));
						t = (t === undefined ? null : parseInt(t));
						data.ref = this.get_backlink(b, t, p);
						/*!debug!*/assert(data.ref !== null, "Bad backlink to " + b + "/" + t + "/" + p);

						// Events
						instance.unbind_html_event(obj, "mouseenter");
						instance.unbind_html_event(obj, "mouseleave");
						instance.unbind_html_event(obj, "mousemove");
						instance.unbind_html_event(obj, "click");
						instance.bind_html_event(obj, "mouseenter", data, events.reference_mouseenter, true);
						instance.bind_html_event(obj, "mouseleave", data, events.reference_mouseleave, true);
						instance.bind_html_event(obj, "mousemove", data, events.reference_mousemove, true);
						instance.bind_html_event(obj, "click", data, events.reference_click, true);
					}
				},
				unprepare: function (instance) {
					// Unbind events
					instance.unbind_html_events();

					// Close settings menu
					if (instance.settings_menu != null) {
						instance.settings_menu.close();
					}

					// Trigger prepare
					api.signal("post_unprepare", {
						post: this,
						post_instance: instance
					});
				},

				mark_as_dead_image: function (no_event) {
					if (this.image == null ||this.image.deleted) return;

					if (!no_event) {
						// Event
						var update_data = {
							comment_fully_loaded: false,
							image_deleted: true
						};
						this.thread.post_updated(this, update_data);
					}

					// Update html
					var i, j, c, d;
					j = this.instances.length;
					for (i = 0; i < j; ++i) {
						// Find post comment
						c = this.instances[i].container.children(".post").children(".post_header");
						d = c.find(".post_indicator_deleted_image,.post_indicator_deleted_post");
						if (d.length > 0) {
							d.removeClass("post_indicator_deleted_post").addClass("post_indicator_deleted_image");
						}
						else {
							c.prepend(
								style.e("span", "post_indicator_deleted_image")
							);
						}
					}

					// Deleted
					this.image.deleted = true;
				},
				mark_as_dead: function (no_event) {
					if (this.deleted) return;

					if (!no_event) {
						// Event
						content.trigger("post_delete", {
							post: this,
							context: null,
							thread_context: null
						});
					}

					// Kill references
					var i, j, c, d;
					j = this.replies.length;
					for (i = 0; i < j; ++i) {
						// Kill
						this.replies[i].origin.update_reference(this.replies[i], this_private.mark_as_dead_update_reference_function);

						// Mark as dead if incomplete
						content.change_incomplete_to_dead_reference(this.replies[i]);
					}


					// Update html
					j = this.instances.length
					for (i = 0; i < j; ++i) {
						// Find post comment
						c = this.instances[i].container.children(".post").children(".post_header");
						d = c.find(".post_indicator_deleted_image,.post_indicator_deleted_post");
						if (d.length > 0) {
							d.removeClass("post_indicator_deleted_image").addClass("post_indicator_deleted_post");
						}
						else {
							c.prepend(
								style.e("span", "post_indicator_deleted_post")
							);
						}
					}

					// Deleted
					this.deleted = true;

					// Kill backlinks
					this.update_all_backlink_references();
				},

				create_inline: function (parent) {
					// Clone
					var c = this.instances[this.instances.length - 1].container;
					c = c.clone();
					var inner = c.children(".post");

					// Remove highlight
					c.removeClass("highlighted focused");

					// Remove any inline containers open within this one
					c.find(".post_container.inline").remove();

					// Remove anything that shouldn't be in an inline container
					c.find(".no_inline").remove();

					// Un-highlight
					Post.get_html_references(c).removeClass("highlighted open");
					Post.get_html_backlinks(c).removeClass("highlighted open");

					// Setup indication of inline
					c.addClass("inline");

					// De-op
					if (!c.hasClass("reply")) {
						// Add reply class
						c.addClass("reply").removeClass("op");

						// Shift around stuff
						var h = inner.children(".post_header");
						var blc = inner.children(".post_embedded_backlink_container");
						var ref_container = inner.children(".post_file_info,.post_image_container");
						if (ref_container.length > 0) {
							ref_container = ref_container.first();
							ref_container.before(h);
							h.after(blc);
						}
					}

					// Remove thread expansion notification
					var n_container = c.find(".post_notifications");
					n_container.children(".post_notification.omitted").remove();
					if (n_container.children().length == 0) {
						n_container.remove();
					}

					// Add to list
					var instance = new Post.Instance(this, c);
					instance.parent = parent || null;
					this.instances.push(instance);

					// Remove image embed
					this.set_image_embedded(false, instance, null);

					// Setup events
					this.prepare(instance);

					// Done
					return instance;
				},
				create_inline_message: function () {
					var e = style.e("div", "post_container reply inline");

					e.append(
						style.e("div", "post")
						.append(
							style.e("div", "post_header")
							.append(
								style.e("span", "post_subject")
							)
							.append(
								style.e("div", "post_header_progress no_spacing")
								.append(
									style.e("div", "post_header_progress_inner")
								)
							)
						)
						.append(
							style.e("div", "post_body")
						)
					);

					return e;
				},

				get_name_plaintext: function () {
					var s = this.instances[0].container.children(".post").children(".post_header");
					var name = s.children(".post_identity").children(".post_identity_name");

					if (name.length == 0) return null;
					return xch.dom_to_string(name);
				},
				get_tripcode_plaintext: function () {
					var s = this.instances[0].container.children(".post").children(".post_header");
					var tripcode = s.children(".post_identity").children(".post_identity_tripcode");

					if (tripcode.length == 0) return null;
					return xch.dom_to_string(tripcode);
				},
				get_subject_plaintext: function () {
					/*!debug!*/assert(this.instances[0], "Bad instance");
					var s = this.instances[0].container.children(".post").children(".post_header");
					var subject = s.children(".post_subject");

					if (subject.length == 0) return null;
					return xch.dom_to_string(subject);
				},
				get_comment_plaintext: function () {
					var b = this.instances[0].container.children(".post");
					var body = b.children(".post_body_full");
					if (body.length == 0) body = b.children(".post_body");

					if (body.length == 0) return null;
					return xch.dom_to_string(body, this_private.get_comment_plaintext_dom_filter);
				},

				update_all_reply_references: function () {
					// Update references
					for (var i = 0, j = this.replies.length; i < j; ++i) {
						// Update
						this.replies[i].origin.update_reference(this.replies[i], null);
					}
				},
				update_all_backlink_references: function () {
					// Update references
					for (var i = 0, j = this.references.length; i < j; ++i) {
						// Update
						if (this.references[i].target !== null) {
							this.references[i].target.update_backlink(this.references[i], null);
						}
					}
				},

				loaded: function () {
					return (this.instances[0] != null);
				},

				insert_into_thread: function () {
					// Insert into thread
					xch.insert_dom_element_sorted(this.thread.container, this.instances[0].container, this_private.post_insert_sort_function);
				},

				scroll_to: function (container) {
					var top = container.offset().top;
					var height = container.height();
					var win_height = $(window).outerHeight();
					var min_top = -content.header.get_screen_area().top;
					$(document).scrollTop(top + Math.min(min_top, (height - win_height) / 2));
				},

				set_image_embedded: function (embedded, instance, obj) {
					if (this.image != null && this.image.url !== null) {
						if (!obj) {
							obj = instance.container.children(".post").children(".post_image_container");
						}
						if (obj.length > 0) {
							if (embedded) {
								if (!obj.hasClass("embed")) {
									// Embed
									obj.addClass("embed");
									// Add
									var img;
									var link = obj.children(".post_image_link");
									if (!this.image.spoiler && this.image.extension != ".gif") {
										link.append(
											style.e("div", "post_image_thumbnail")
											.css({
												"background-image": 'url("' + style.escape_string(this.image.thumbnail) + '")',
												"background-size": this.image.width + "px " + this.image.height + "px"
											})
										);
									}
									var is_video = false;
									for (var m_type in xch.mime_types) {
										for (var i = 0; i < xch.mime_types[m_type].extensions.length; ++i) {
											if (xch.mime_types[m_type].extensions[i] == this.image.extension) {
												is_video = (xch.video_mime_types.indexOf(m_type) >= 0);
											}
										}
									}
									if (is_video) {
										// Video
										link.append(
											(img = style.e("video", "post_image full"))
											.attr("loop", "")
											.attr("autoplay", "")
											.on("load", { obj: obj }, this_private.on_embedded_post_image_load)
											.attr("src", this.image.url)
										);
									}
									else {
										// Image
										link.append(
											(img = style.e("img", "post_image full"))
											.attr("title", "")
											.attr("alt", "")
											.on("load", { obj: obj }, this_private.on_embedded_post_image_load)
											.attr("src", this.image.url)
										);
										if (this.image.width > 0 && this.image.height > 0) {
											img.css({
												width: this.image.width + "px",
												height: this.image.height + "px"
											});
										}
									}
								}
							}
							else {
								if (obj.hasClass("embed")) {
									// Un-embed
									obj.removeClass("embed loaded");
									// Remove
									var link = obj.children(".post_image_link");
									link.children(".post_image.full").off("load").remove();
									link.children(".post_image_thumbnail").remove();
								}
							}
						}
					}
				},

				references_me: function () {
					for (var i = 0, ref; i < this.references.length; ++i) {
						ref = this.references[i];
						if (ref.target_thread_id !== null && content.posted_by_me(ref.target_board, ref.target_thread_id, ref.target_post_id)) {
							return true;
						}
					}
					return false;
				},

				update_html_backlink_location: function (in_footer) {
					var post, header, footer, backlinks, embed_container;
					for (var i = 0; i < this.instances.length; ++i) {
						footer = (post = this.instances[i].container.children(".post")).children(".post_footer");
						if (footer.hasClass("hidden") == in_footer) {
							// Get the header
							header = post.children(".post_header");

							// Change
							if (in_footer) {
								backlinks = header.children(".post_backlinks");
								embed_container = post.children(".post_embedded_backlink_container");

								if (backlinks.hasClass("empty")) {
									backlinks.removeClass("empty");
									footer.addClass("empty");
								}
								else {
									footer.removeClass("empty");
								}

								footer.removeClass("hidden").append(backlinks).append(embed_container);
							}
							else {
								backlinks = footer.children(".post_backlinks");
								embed_container = footer.children(".post_embedded_backlink_container");

								if (footer.hasClass("empty")) {
									backlinks.addClass("empty");
								}
								else {
									footer.addClass("empty");
								}

								header.after(embed_container);
								header.children(".post_settings").before(backlinks);
								footer.addClass("hidden");
							}
						}
					}
				}

			};

			Post.get_html_references = function (container, additional_selector) {
				return container
					.children(".post")
					.children(".post_body")
					.find(".post_reference" + (additional_selector || ""));
			};
			Post.get_html_backlinks = function (container, additional_selector) {
				var con = container.children(".post");
				return con.children(".post_header")
						.add(con.children(".post_footer:not(.hidden)")).find(".post_backlinks")
						.find(".post_backlink" + (additional_selector || ""));
			};

			Post.Reference = (function () {

				var Reference = function (origin, target_board, target_thread_id, target_post_id) {
					this.origin = origin;
					this.target = null;
					this.target_board = target_board;
					this.target_thread_id = target_thread_id;
					this.target_post_id = target_post_id;
					this.target_dead = (target_thread_id === null) && (origin.thread.board != target_board || origin.id > target_post_id);

					// Forward-reference
					origin.references.push(this);

					// Back-reference
					var ref;
					if (target_thread_id !== null) {
						if ((ref = content.get_thread(target_thread_id, target_board)) !== null && (ref = ref.get_post(target_post_id)) !== null) {
							this.target = ref;
							ref.replies.push(this);
						}
					}

					// Add to incomplete list if necessary
					if (this.target == null) {
						if (this.target_dead) {
							content.add_dead_reference(this);
						}
						else {
							content.add_incomplete_reference(this);
						}
					}
				};

				Reference.prototype = {

					constructor: Reference,

					equals: function (origin, target_board, target_thread_id, target_post_id) {
						return (
							this.origin == origin &&
							this.target_post_id == target_post_id &&
							(target_thread_id === null || this.target_thread_id == target_thread_id) &&
							this.target_board == target_board
						);
					},
					complete: function (target) {
						// Update
						this.target = target;
						this.target_board = target.thread.board;
						this.target_thread_id = target.thread.id;
						this.target_post_id = target.id;
						this.target_dead = false;
						// Add to reply list
						target.replies.push(this);
					},
					forwards_death: function () {
						if (this.target_thread_id === null && this.origin.id < this.target_post_id) {
							this.target_dead = true;
							return true;
						}
						return false;
					}

				};

				Reference.find = function (origin, target_board, target_thread_id, target_post_id) {
					for (var i = 0; i < origin.references.length; ++i) {
						if (origin.references[i].equals(origin, target_board, target_thread_id, target_post_id)) return origin.references[i];
					}
					return null;
				};

				return Reference;

			})();

			Post.Instance = (function () {

				var Instance = function (post, container) {
					this.parent = null;
					this.post = post;
					this.container = container;
					this.inlined_instances = [];
					this.html_events = [];
					this.settings_menu = null;
				};

				Instance.prototype = {

					constructor: Instance,

					destroy: function () {
						// Remove from parent
						var list, i, j;
						if (this.parent != null) {
							for (list = this.parent.inlined_instances, i = 0, j = list.length; i < j; ++i) {
								if (list[i] == this) {
									list.splice(i, 1);
									break;
								}
							}
							/*!debug!*/assert(i < j, "Not deleted from parent");
						}

						// Remove inlined instances
						for (j = this.inlined_instances.length, i = j - 1; i >= 0; --i) {
							this.inlined_instances[i].destroy();
						}

						// Remove html events
						this.post.unprepare(this);

						// Remove from list
						for (list = this.post.instances, i = 0, j = list.length; i < j; ++i) {
							if (this == list[i]) {
								list.splice(i, 1);
								break;
							}
						}
						/*!debug!*/assert(i < j, "Container does not match post");

						// Remove from html
						this.container.remove();
						this.container = null;
					},

					add_inlined_instance: function (instance) {
						this.inlined_instances.push(instance);
					},
					remove_inlines: function () {
						for (var j = this.inlined_instances.length, i = j - 1; i >= 0; --i) {
							this.inlined_instances[i].destroy();
						}
					},

					unbind_html_events: function () {
						for (var i = 0; i < this.html_events.length; ++i) {
							// Turn off event
							$(this.html_events[i].object).off(this.html_events[i].event, this.html_events[i].callback);
						}
						this.html_events = [];
					},
					unbind_html_event: function (obj, event, maintained) {
						for (var i = 0, j; i < obj.length; ++i) {
							for (j = 0; j < this.html_events.length; ++j) {
								if (this.html_events[j].object == obj[i] && this.html_events[j].event == event) {
									// Turn off event
									$(obj[i]).off(event, this.html_events[j].callback);
									// Remove
									this.html_events.splice(j, 1);
									break;
								}
							}
						}
					},
					bind_html_event: function (obj, event, data, callback, data_maintained) {
						obj.on(event, data, callback);

						// Run on each one
						for (var i = 0; i < obj.length; ++i) {
							/*!debug!*/assert(this.get_html_event(obj[i], event) === null, "Duplicate event");
							// Add to list
							var h_e = {
								object: obj[i],
								event: event,
								callback: callback
							};
							if (data_maintained) h_e.data = data;
							this.html_events.push(h_e);
						}
					},
					get_html_event: function (obj, event) {
						for (var i = 0; i < this.html_events.length; ++i) {
							if (this.html_events[i].object == obj && this.html_events[i].event == event) return this.html_events[i];
						}
						return null;
					}

				};

				return Instance;

			})();

			return Post;

		})(),

		// Post image
		Image: (function () {

			var Image = function () {
				this.url = null;
				this.thumbnail = null;
				this.extension = null;
				this.filename_server = null;
				this.filename_original = null;
				this.width = 0;
				this.height = 0;
				this.thumbnail_width = 0;
				this.thumbnail_height = 0;
				this.filesize = 0;
				this.filesize_label = null;
				this.spoiler = false;
				this.deleted = false;
			};

			var this_private = {

				get_point_in_bounds: function (left, top, width, height, px, py) {
					// Update borders
					var lr = Math.min(style.image_preview.zoom_padding.pixels.lr, style.image_preview.zoom_padding.percent.lr * width);
					var tb = Math.min(style.image_preview.zoom_padding.pixels.tb, style.image_preview.zoom_padding.percent.tb * height);
					left += lr;
					top += tb;
					width -= lr * 2;
					height -= tb * 2;

					// Get percents
					var p = {
						x: (px - left) / width,
						y: (py - top) / height
					};
					if (p.x < 0.0) p.x = 0.0;
					else if (p.x > 1.0) p.x = 1.0;
					if (p.y < 0.0) p.y = 0.0;
					else if (p.y > 1.0) p.y = 1.0;

					// Done
					return p;
				},

				bound_image: function (obj, bounding, left, top, width, height, px, py, init) {
					var eb = style.image_preview.zoom_padding.out_of_bounds;

					if ((bounding.flags & 1) != 0) {
						// Extend right
						var r = (px - (left + width)) + eb;
						if (r <= 0) {
							bounding.flags &= ~1;
							r = "";
						}
						else {
							if (init) bounding.right = r;
							else if (r > bounding.right) r = bounding.right;
							r = r.toFixed(2) + "px";
						}
						obj.css("padding-right", r);
					}

					if ((bounding.flags & 2) != 0) {
						// Extend up
						var r = (top - py) + eb;

						if (r <= 0) {
							bounding.flags &= ~2;
							obj.css("padding-top", "").css("top", "");
						}
						else {
							if (init) bounding.top = r;
							else if (r > bounding.top) r = bounding.top;
							r = r.toFixed(2) + "px";
							obj.css("padding-top", r).css("top", "-" + r);
						}
					}
					else if ((bounding.flags & 4) != 0) {
						// Extend up
						var r = (py - (top + height)) + eb;
						if (r <= 0) {
							bounding.flags &= ~4;
							r = "";
						}
						else {
							if (init) bounding.bottom = r;
							else if (r > bounding.bottom) r = bounding.bottom;
							r = r.toFixed(2) + "px";
						}
						obj.css("padding-bottom", r);
					}
				},

				cancel_event: function (event) {
					// Cancel left clicks
					return (event.which != 1);
				},

				on_preview_mousemove: function (event) {
					// Positions
					var event_data = event.data.event_data;
					var me = $(this);
					var inner = me.children(".image_preview_inner_3");
					var pos = inner.offset();

					// Percent
					if (event.pageX !== undefined) {
						event_data.mouse_last.x = event.pageX;
						event_data.mouse_last.y = event.pageY;
					}
					else {
						event.pageX = event_data.mouse_last.x;
						event.pageY = event_data.mouse_last.y;
					}
					var p = this_private.get_point_in_bounds.call(event.data.self, pos.left, pos.top, inner.width(), inner.height(), event.pageX, event.pageY);

					// Size
					var max = (settings.image_preview.zooming.invert ? 100 : -100);
					inner.find(".image_preview_size").css({ // Range: -50% to 50% for the zoomed offset
						left: ((p.x - 0.5) * max).toFixed(2) + "%",
						top: ((p.y - 0.5) * max).toFixed(2) + "%"
					});

					// Bounded movement
					if (event_data.bounding && event_data.bounding.flags) {
						this_private.bound_image.call(event.data.self, me, event_data.bounding, pos.left, pos.top, inner.width(), inner.height(), event.pageX, event.pageY, false);
					}
				},
				on_preview_mouseenter: function (event) {
					event.data.self.close_preview_cancel(event.data.event_data);
				},
				on_preview_mouseleave: function (event) {
					event.data.self.close_preview(event.data.event_data);
				},
				on_preview_mousescroll: function (event) {
					// Zoom in/out
					var delta = Math.max(-1, Math.min(1, (event.originalEvent.wheelDelta || -event.originalEvent.detail || 0)));
					this_private.set_preview_zoom.call(event.data.self, event.data.event_data.zoom + delta, event.data.obj, event.data.event_data, event);

					// Ignore event
					return false;
				},
				on_preview_click: function (event) {
					if (event.which != 1) return true;

					event.data.self.destroy_preview(event.data.event_data);
				},

				on_preview_image_load: function (event) {
					// Hide thumbnail
					event.data.thumbnail.addClass("hidden");
				},
				on_preview_image_error: function (event) {
					// Error message
					$(this).addClass("hidden");
					event.data.thumbnail.removeClass("hidden").addClass("main");
					event.data.image_container.children(".info").prepend(
						style.e("span", "error").text("Error loading image")
					);
				},

				set_preview_zoom: function (zoom, obj, event_data, event) {
					// Check if update is needed
					if (zoom < 1) zoom = 1;
					if (zoom == event_data.zoom || event_data.preview == null) return;
					event_data.zoom = zoom;

					// Change zoom/size
					this.position_preview(obj, event_data.preview, event_data, true);

					// Update zoom position
					var outer = event_data.preview.find(".image_preview_inner_2");
					if (outer.length > 0) {
						this_private.on_preview_mousemove.call(outer[0], event);
					}
				}

			};

			Image.prototype = {

				constructor: Image,

				parse: function (html, img, info) {
					// Has info html
					if (html != null) {
						// URL
						var ia = html.find("a:nth-of-type(1)");
						this.url = ia.attr("href") || "";
						this.extension = this.url.split(".").pop().toLowerCase();
						if (this.extension) this.extension = "." + this.extension;

						// Server filename
						this.filename_server = ia.html() || "";

						// Details
						var d = html.find("span.unimportant");
						var str = $(d.contents()[0]).text();
						var match = /\((Spoiler\s+Image,\s+)?([0-9.]+)\s+([^,]+)(?:,\s+([0-9]+)x([0-9]+))?,\s*((.+?)\))?/i.exec(str);

						// Spoiler
						if (match[1]) this.spoiler = true;

						// File size
						this.filesize_label = [ match[2] , match[3].trim().toUpperCase() ];
						this.filesize = Math.round(parseFloat(this.filesize_label[0]) * (this.filesize_label[1] == "MB" ? 1048576 : (this.filesize_label[1] == "KB" ? 1024 : 1) ));

						// Dimensions
						if (match[4] != null) {
							this.width = parseInt(match[4]);
							this.height = parseInt(match[5]);
						}

						// Original filename
						var ds = d.children("span.postfilename");
						if (ds.length > 0) {
							this.filename_original = ds.attr("title") || ds.text();
						}
						else {
							this.filename_original = (match[7] || "").trim();
						}
					}

					// Has img html
					if (img != null) {
						// Thumbnail
						this.thumbnail = img.attr("src");

						// Size
						var s = img.attr("style");
						if (s) {
							var m = /width:\s*([0-9]+)px/i.exec(s);
							if (m) this.thumbnail_width = parseInt(m[1]);
							m = /height:\s*([0-9]+)px/i.exec(s);
							if (m) this.thumbnail_height = parseInt(m[1]);
						}

						// Deleted
						this.deleted = info.urls_match(info.images.deleted.url, this.thumbnail);

						// Size
						if (this.deleted) {
							this.thumbnail_width = info.images.deleted.size.width;
							this.thumbnail_height = info.images.deleted.size.height;
						}
					}
				},

				stylize_size: function (obj, is_op) {
					var limit = (is_op ? style.post.image.op.thumbnail.size : style.post.image.reply.thumbnail.size);
					var scale = true;
					if (this.url === null) {
						// Deleted
						if (!limit.deleted) scale = false;
					}
					else if (this.spoiler) {
						// Spoilered
						if (!limit.spoiler) scale = false;
					}

					// Resize
					if (scale) {
						var w = this.thumbnail_width;
						var h = this.thumbnail_height;
						if (w > 0 && h > 0) {
							scale = Math.max(w / limit.width, h / limit.height);
							if (scale > 1.0) {
								w /= scale;
								h /= scale;
							}
							obj.css({
								width: w.toFixed(2) + "px",
								height: h.toFixed(2) + "px"
							});
						}
					}
				},

				create_preview: function (obj, event_data, click_event) {
					var self = this;

					// Settings
					event_data.zoom = 1;
					event_data.bounding = null;

					// Object data
					var obj_pos = obj.offset();
					var obj_width = obj.width();
					var obj_height = obj.height();

					// Create
					var container, image_container, outer, connector, thumbnail, image;
					(container = style.e("div", "image_preview"))
					.append( //{
						(image_container = style.e("div", "image_preview_inner_1"))
						.append(
							(outer = style.e("div", "image_preview_inner_2"))
							.append(
								style.e("div", "image_preview_inner_3")
								.append(
									style.e("div", "image_preview_origin")
									.append(
										style.e("div", "image_preview_size")
										.append(
											style.e("div", "image_preview_image_container")
											.append(
												(thumbnail = style.e("div", "image_preview_thumbnail"))
											)
											.append(
												(image = style.e("img", "image_preview_image"))
												.attr("title", "")
												.attr("alt", "")
											)
											.append(
												style.e("div", "image_preview_overlay")
											)
										)
									)
								)
							)
							.append(
								style.e("div", "image_preview_info")
								.append(
									style.e("span", "image_preview_info_item dimensions")
									.html(this.width + "<i>" + style.symbols.mult + "</i>" + this.height)
								)
								.append(
									style.e("span", "image_preview_info_item zoom")
								)
							)
						)
					) //}
					.append(
						(connector = style.e("div", "image_preview_connector"))
						.css({
							width: style.image_preview.padding.left + "px",
							height: obj_height + "px",
							left: (obj_pos.left + obj_width) + "px",
							top: obj_pos.top + "px"
						})
					);

					// Events
					var data = { self: this, event_data: event_data };
					outer.add(connector)
					.on("mouseenter", data, this_private.on_preview_mouseenter)
					.on("mouseleave", data, this_private.on_preview_mouseleave)
					.on("mousedown", this_private.cancel_event);
					outer.on("mousemove", data, this_private.on_preview_mousemove);
					if (click_event) {
						outer.on("click", { obj: obj, preview_data: event_data }, click_event);
					}
					if (settings.image_preview.zooming.enabled) {
						outer.on("mousewheel DOMMouseScroll", { self: this, event_data: event_data, obj: obj }, this_private.on_preview_mousescroll);
					}

					data = { thumbnail: thumbnail, image_container: image_container };
					image
					.on("load", data, this_private.on_preview_image_load)
					.on("error", data, this_private.on_preview_image_error);

					// Thumbnail
					if (this.spoiler) {
						thumbnail.addClass("hidden");
					}
					else {
						thumbnail.css("background-image", 'url("' + style.escape_string(this.thumbnail) + '")');
						if (this.extension == ".gif") {
							thumbnail.addClass("hidden");
						}
					}

					// Image
					image.attr("src", this.url);

					// Position
					this.position_preview(obj, container, event_data, false);

					// Done
					return container;
				},
				position_preview: function (obj, container, event_data, mouse_bounded) {
					// Objects
					var image_container = container.children(".image_preview_inner_1");
					var outer = image_container.children(".image_preview_inner_2");
					var c_origin = outer.find(".image_preview_origin");
					var c_size = c_origin.children(".image_preview_size");
					var preview = c_size.children(".image_preview_image_container");
					var thumbnail = preview.children(".image_preview_thumbnail");
					var image = preview.children(".image_preview_image");

					// Object position
					var win = $(window);
					var win_scroll = {
						top: win.scrollTop(),
						left: win.scrollLeft()
					};
					var obj_pos = obj.offset();
					var obj_height = obj.height();
					var obj_width = obj.width();
					obj_pos.top -= win_scroll.top;
					obj_pos.left -= win_scroll.left;

					// Zoom
					image_container.find(".image_preview_info_item.zoom").text((event_data.zoom * 100) + "%");

					// Paddings
					var l_o = (obj_pos.left + obj_width + style.image_preview.padding.left);
					var bounds = content.header.get_screen_area();
					bounds.top += style.image_preview.padding.top;
					bounds.left += l_o;
					bounds.width -= l_o + style.image_preview.padding.right;
					bounds.height -= style.image_preview.padding.top + style.image_preview.padding.bottom;

					// Sizes
					if (bounds.width <= 0) bounds.width = 1;
					if (bounds.height <= 0) bounds.height = 1;
					var w = this.width;
					var h = this.height;
					var scale = Math.min(bounds.width / w, bounds.height / h);
					if (!settings.image_preview.upscale) scale = Math.min(1, scale);

					// Size CSS
					w = this.width * scale * event_data.zoom;
					h = this.height * scale * event_data.zoom;
					var size_css = {
						width: w.toFixed(2) + "px",
						height: h.toFixed(2) + "px"
					};
					c_size.css(size_css);

					// Outer CSS size
					var w_outer = Math.min(w, bounds.width);
					var h_outer = Math.min(h, bounds.height);
					var size_css_outer = {
						width: w_outer.toFixed(2) + "px",
						height: h_outer.toFixed(2) + "px"
					};
					c_origin.css({
						width: (w - w_outer).toFixed(2) + "px",
						height: (h - h_outer).toFixed(2) + "px"
					});

					// Position
					var left = (bounds.left);
					var top = (obj_pos.top + obj_height / 2) - h_outer / 2;
					var limit;
					if (top > (limit = bounds.top + bounds.height - h_outer)) top = limit;
					else if (top < bounds.top) top = bounds.top;
					image_container.css({
						left: left.toFixed(2) + "px",
						top: top.toFixed(2) + "px"
					});

					// Mouse bounding
					if (mouse_bounded && settings.image_preview.zooming.bounded) {
						var b = 0;
						if (event_data.mouse_last.x > left + w_outer + win_scroll.left) {
							b |= 1;
						}
						if (event_data.mouse_last.y < top + win_scroll.top) {
							b |= 2;
						}
						else if (event_data.mouse_last.y > top + h_outer + win_scroll.top) {
							b |= 4;
						}
						if (b > 0) {
							event_data.bounding = {
								flags: b
							};
							this_private.bound_image(outer, event_data.bounding, left + win_scroll.left, top + win_scroll.top, w, h, event_data.mouse_last.x, event_data.mouse_last.y, true);
						}
					}

					// Apply sizes
					if (!this.spoiler) {
						thumbnail.css("background-size", size_css.width + " " + size_css.height);
					}
					outer.css(size_css_outer);
				},
				close_preview_cancel: function (event_data) {
					if (event_data.close_timer !== null) {
						clearTimeout(event_data.close_timer);
						event_data.close_timer = null;
					}
				},
				close_preview: function (event_data) {
					if (event_data.close_timer === null) {
						var self = this;

						event_data.close_timer = setTimeout(function () {
							event_data.close_timer = null;
							self.destroy_preview(event_data);
						}, 50);
					}
				},
				destroy_preview: function (event_data) {
					if (event_data.preview != null) {
						// Stop events
						event_data.preview.find(".connector").off("mouseenter mouseleave mousedown");
						event_data.preview.find(".outer").off("mouseenter mouseleave mousemove mousedown mousewheel DOMMouseScroll");
						event_data.preview.find("img.image").off("load error");

						// Remove
						event_data.preview.remove();
						event_data.preview = null;
					}
				},

				good: function () {
					return (this.url != null);
				},

				previewable: function () {
					// Spoiler check
					var en = settings.image_preview.enabled;
					if (this.spoiler && !en.spoiler) return false;
					// Extension check
					var ext = this.extension;
					if (!(ext in en)) ext = settings.image_preview["default"];
					return en[ext];
				}

			};

			return Image;

		})(),

		// Styling
		Style: (function () {

			var Style = function (head) {
				this.cls = "xch";
				this.css = new Style.CSS();

				this.top_gradient = {
					0: "#d6daf0",
					100: "#eef2ff",
					direction: "t2b",
					height: "128px"
				};

				this.body = {
					colors: {
						bg: "#eef2ff"
					},
					font: {
						face: "arial,helvetica,sans-serif",
						size: "14px",
						style: "normal",
						weight: "normal"
					},
					content_padding: {
						top: 32,
						bottom: 64
					}
				};

				this.thread = {
					separator: {
						height: 8,
						size: 4,
						style: "dashed",
						color: "#d6daf0"
					},
					padding: {
						left: 20,
						top: 0,
						right: 20,
						bottom: 0
					}
				};

				this.header = {
					background: {
						opacity: 0.75,
						gradient: {
							0: "#d6daf0",
							100: "#b7c5d9",
							direction: "t2b"
						},
						shadow: {
							color: "#34345c",
							offset: {
								left: 0,
								top: 0
							},
							blur: 4,
							size: 2
						}
					},
					font: {
						face: "arial,helvetica,sans-serif",
						size: "14px",
						style: "normal",
						weight: "normal"
					},
					padding: 4,
					spacing: {
						group: 6,
						board: 3,
						options: 6
					},
					separators: {
						group_before: "[",
						group_after: "]",
						group_inner: "/"
					},
					menu: {
						background: {
							color: "#b7c5d9",
							opacity: 0.75
						}
					},
					boards: {
						font: {},
						colors: {
							group: "#5c6f85",
							separator: "#5c6f85",
							link: "#0f0c5c",
							link_hover: "#dd0000"
						},
						current: {
							colors: {
								link: "#f8f8f8",
								link_hover: "#f8f8f8"
							},
							shadow: {
								color: "#0f0c5c",
								offset: {
									left: 0,
									top: 0
								},
								blur: 2,
								factor: 3,
								hover: {
									color: "#dd0000",
									offset: {
										left: 0,
										top: 0
									},
									blur: 2,
									factor: 3
								}
							},
							font: { weight: "bold" }
						}
					},
					options: {
						colors: {
							link: "#0f0c5c",
							link_hover: "#dd0000"
						}
					}
				};

				this.post = {

					separation: 8,

					font: {
						face: "arial,helvetica,sans-serif",
						size: "14px",
						style: "normal",
						weight: "normal"
					},

					border: {
						top: 0,
						right: 1,
						bottom: 1,
						left: 0,
						style: "solid",
						radius: {
							topleft: 0,
							topright: 10,
							bottomright: 10,
							bottomleft: 10
						}
					},

					colors: {
						text: "#080808",
						bg: "#d6daf0",
						bg_hl: "#d6bad0",
						border: "#b7c5d9",
						border_hl: "#b7a0b0",
						link: {
							text: "#34345c",
							text_hover: "#dd0000"
						}
					},

					header: {
						spacing: 6,
						padding: {
							left: 4,
							top: 4,
							right: 4,
							bottom: 4
						},
						colors: {
							bg: "#b7c5d9",
							bg_hl: "#b7a0b0",
							border: "#ffffff",
							border_hl: "#ffffff"
						},
						font: {},
						border: {
							top: 0,
							right: 0,
							bottom: 1,
							left: 0,
							style: "solid"
						},
						options: {
							separator: {
								start: "[",
								end: "]"
							},
							text: {
								reply_to: "Reply"
							},
							colors: {
								text: "#8899aa",
								link: "#34345c",
								link_hover: "#dd0000"
							},
							spacing: {
								inner: 3
							},
							font: {}
						},
						backlinks: {
							label: "",
							spacing: 6,
							spacing_first: 0,
							font: {},
							colors: {
								text: "#dd0000",
								text_hover: "#ff8800",
								text_open: "#ff8800",
								text_open_hover: "#ff8800",
								text_hl: "#ff8800"
							}
						},
						subject: {
							color: "#0f0c5c",
							font: { weight: "bold" }
						},
						time: {
							font: {}
						},
						identity: {
							font: {},
							name: {
								font: { weight: "bold" },
								colors: {
									user: "#117743",
									mod: "#800080",
									admin: "#ff0000"
								}
							},
							tripcode: {
								font: {}
							},
							capcode: {
								indicator: "##",
								font: { weight: "bold" }
							},
							email_colors: {
								text:"#0f0c5c",
								text_hover:"#dd0000"
							},
							spacing: 4
						},
						icons: {
							color: "#34345c"
						},
						number: {
							indicator: "No.",
							font: {},
							colors: {
								text:" #080808",
								text_hover: "#dd0000"
							}
						},
						settings: {
							opacity: {
								normal: 0,
								hover: 1,
								transition: {
									time: 0.125,
									method: "linear",
									delay: 0
								}
							}
						}
					},

					file_info: {
						spacing: 6,
						padding: {
							left: 20,
							top: 12,
							right: 20,
							bottom: 4
						},
						font: { size: "10px" },
						file_name: {
							font: {}
						},
						attributes: {
							font: {},
							prefix: "(",
							suffix: ")"
						},
						file_extras: {
							font: {},
							spacing: 4
						}
					},

					image: {
						op: {
							thumbnail: {
								size: {
									width: 255,
									height: 255,
									spoiler: false,
									deleted: false
								},
								shadow: {
									color: "#d6daf0",
									offset: {
										left: 0,
										top: 0
									},
									blur: 2,
									size: 1
								}
							},
							full: {
								shadow: {
									color: "#d6daf0",
									offset: {
										left: 0,
										top: 0
									},
									blur: 4,
									size: 2
								}
							},
							bg: "#eef2ff"
						},
						reply: {
							thumbnail: {
								size: {
									width: 125,
									height: 125,
									spoiler: true,
									deleted: false
								},
								shadow: {
									color: "#b7c5d9",
									offset: {
										left: 0,
										top: 0
									},
									blur: 1,
									size: 1
								}
							},
							full: {
								shadow: {
									color: "#b7c5d9",
									offset: {
										left: 0,
										top: 0
									},
									blur: 4,
									size: 2
								}
							},
							bg: "#d6daf0"
						},
						padding: {
							left: 20
						}
					},

					comment: {
						font: {},
						padding: {
							left: 20,
							top: 12,
							right: 20,
							bottom: 12
						},
						colors: {
							text: "#080808",
							spoiler: {
								text: "#080808",
								bg: "#080808",
								text_hover: "#f8f8f8",
								bg_hover: "#080808"
							},
							quote: {
								text: "#789922",
								text_hover: "#789922"
							},
							reply_link: {
								text: "#dd0000",
								text_hover: "#ff8800",
								text_open: "#ff8800",
								text_open_hover: "#ff8800",
								text_hl: "#ff8800"
							},
							link: {
								text: "#34345c",
								text_hover: "#dd0000"
							}
						}
					},

					footer: {
						font: { size: "10px" },
						padding: {
							top_outer: 4,
							top_inner: 4,
							left_inner: 10,
							left_outer: 10,
							right_inner: 10,
							right_outer: 10,
							bottom: 4,
						},
						border: {
							size: 1,
							style: "solid",
							colors: {
								op: "#d6daf0",
								op_hl: "#d6bad0",
								reply: "#ffffff",
								reply_hl: "#ffffff"
							}
						},
						backlinks: {
							label: "Replies:",
							spacing: 4,
							spacing_first: 4,
							font: {},
							colors: {
								text: "#dd0000",
								text_hover: "#ff8800",
								text_open: "#ff8800",
								text_open_hover: "#ff8800",
								text_hl: "#ff8800"
							}
						}
					},

					notifications: {
						font: { size: "12px" },
						spacing: {
							outer: 6,
							inner: 2
						},
						padding: {
							top: 4
						},
						separator: {
							start: "[",
							end: "]"
						},
						text: {
							too_long: "Message too long",
							too_long_expand: "Expand message",
							too_long_shrink: "Shrink message",
							omitted_hidden_prefix: "",
							omitted_hidden_suffix: " omitted",
							omitted_visible_prefix: "Hide ",
							omitted_visible_suffix: ""
						},
						colors: {
							text: "#8899aa",
							link: "#34345c",
							link_hover: "#dd0000"
						},
						progress: {
							colors: {
								ajax: "#3191b7",
								parse: "#9131b7"
							},
							opacity: 0.25
						}

					},

					backlink_indicator: {
						same_board: ">>%post_id",
						cross_board: ">>>/%board/%post_id",
						suffix_list: {
							same_board: [
								["you","you"],
								["self","self"],
								["op","op"],
								["past","past"],
								["cross_thread","cross-thread"],
								["dead","dead"]
							],
							cross_board: [
								["you","you"],
								["self","self"],
								["op","op"],
								["past","past"],
								["dead","dead"]
							]
						},
						suffix_first: " (",
						suffix_separator: ", ",
						suffix_last: ")"
					},

					reference_indicator: {
						same_board: ">>%post_id",
						cross_board: ">>>/%board/%post_id",
						suffix_list: {
							same_board: [
								["you","you"],
								["self","self"],
								["op","op"],
								["future","future"],
								["cross_thread","cross-thread"],
								["dead","dead"]
							],
							cross_board: [
								["you","you"],
								["self","self"],
								["op","op"],
								["future","future"],
								["dead","dead"]
							]
						},
						suffix_first: " (",
						suffix_separator: ", ",
						suffix_last: ")"
					},

					deleted_indicator: {
						post_text: "[Post Deleted]",
						image_text: "[Image Deleted]",
						color: "#ff0000",
						font: {
							weight: "bold",
							style: "italic"
						}
					},

					load_bar: {
						height: 4,
						colors: {
							ajax: "#3191b7",
							parse: "#9131b7"
						}
					},

					inline: {
						container_padding: {
							left: 8,
							top: 8,
							right: 8,
							bottom: 0
						},
						floating: {
							offsets: {
								x: 8,
								y: 8
							},
							shadow: {
								color: "#34345c",
								offset: {
									left: 0,
									top: 0
								},
								blur: 5,
								size: 5
							}
						},
						embedded: {
							shadow: {
								color: "#34345c",
								offset: {
									left: 0,
									top: 0
								},
								blur: 2,
								size: 1
							}
						}
					},

					focus: {
						normal: {
							shadow: {
								color: "#dd0000",
								offset: {
									left: 0,
									top: 0
								},
								blur: 3,
								size: 3
							}
						},
						inline: {
							shadow: {
								color: "#dd0000",
								offset: {
									left: 0,
									top: 0
								},
								blur: 2,
								size: 2
							}
						}
					}

				};

				this.image_preview = {
					colors: {
						bg: "rgba(238,242,255,0.625)",
						error: "#ff7777"
					},
					thumbnail: {
						opacity: 0.5
					},
					shadow: {
						color: "#34345c",
						offset: {
							left: 0,
							top: 0
						},
						blur: 10,
						size: 10
					},
					padding: {
						left: 20,
						top: 20,
						bottom: 20,
						right: 20
					},
					zoom_padding: {
						pixels: {
							lr:	20,
							tb: 20
						},
						percent: {
							lr:	0.1,
							tb: 0.1
						},
						out_of_bounds: 10
					},
					header: {
						padding: {
							top: 0,
							left: 0,
							right: 0,
							bottom: 0
						},
						color: "#f8f8f8",
						shadow: {
							color: "#34345c",
							offset: {
								left: 0,
								top: 0
							},
							blur: 2,
							factor: 3
						},
						separator: ", "
					}
				};

				this.checkbox_settings = {
					sizes: {
						tiny: {
							box_size: 10,
							border_width: 1,
							border_radius: 2
						},
						small: {
							box_size: 12,
							border_width: 1,
							border_radius: 2
						},
						normal: {
							box_size: 14,
							border_width: 1,
							border_radius: 3
						}
					},
					colors: {
						bg: "#ffffff",
						bg_hover: "#ffffff",
						border: "#0f0c5d",
						border_hover: "#34345d",
						check: "#080808",
						check_hover: "#34345d"
					}
				};

				this.custom = {
					selection: "#f8f8f8",
					selection_bg: "#34345c"
				};

				this.symbols = {
					mult: "×",
					minus: "-",
					triangles: {
						right: "â–º",
						down: "â–¼",
						left: "â—„",
						up: "â–²"
					}
				};

				this.menu = {
					colors: {
						bg: "rgba(214,218,240,1)",
						bg_hover: "rgba(214,218,240,1)",
						text: "#0f0c5c",
						text_hover: "#dd0000"
					},
					border: {
						size: 1,
						style: "solid",
						color: "rgba(52,52,92,0.75)",
						color_hover: "#34345c"
					},
					shadow: {
						color: "#34345c",
						offset: {
							left: 0,
							top: 0
						},
						blur: 2,
						size: 1,
						opacity: 0.75
					},
					font: {
						face: "arial,helvetica,sans-serif",
						size: "14px",
						style: "normal",
						weight: "normal"
					},
					font_small: {
						face: "arial,helvetica,sans-serif",
						size: "10px",
						style: "normal",
						weight: "normal"
					},
					padding: {
						left: 4,
						top: 2,
						right: 4,
						bottom: 2
					}
				};

				this.popup = {
					colors: {
						text: "#080808"
					},
					font: {
						face: "arial,helvetica,sans-serif",
						size: "14px",
						style: "normal",
						weight: "normal"
					},
					resizers: {
						size: 8
					},
					bg: {
						size: 4,
						radius: 8,
						opacity: 0.75,
						active_opacity: 1,
						color: "#d6d6f0",
						shadow: {
							color: "#34345c",
							offset: {
								left: 0,
								top: 0
							},
							blur: 2,
							size: 1
						}
					},
					input: {
						padding: 1,
						font: {
							face: "arial,helvetica,sans-serif",
							size: "12px",
							style: "normal",
							weight: "normal"
						},
						colors: {
							bg: "#f8f8f8",
							bg_hover: "#eef2ff",
							bg_selected: "#d6daf0",
							text: "#080808",
							link: "#34345c",
							link_hover: "#dd0000",
							placeholder: "#5c6f85",
							placeholder_opacity: 0.625
						},
						border: {
							size: 1,
							style: "solid",
							color: "#5c6f85",
							color_hover: "#dd0000",
							color_selected: "#34345c",
							radius: 4
						}
					},
					close: {
						color: "#34345c",
						color_hover: "#dd0000",
						padding: 3
					},
					title: {
						font: {
							weight: "bold"
						},
						shadow: {
							offset: {
								left: 0,
								top: 0
							},
							color: "#f8f8f8",
							blur: 2,
							factor: 3
						}
					},
					description: {
						font: {
							size: "12px"
						}
					},
					buttons: {
						spacing: 6,
					},
					separation: 4
				};

				this.qr = {
					label: {
						font: {
							weight: "normal"
						},
						text: {
							reply: "Reply",
							quick_reply: "Quick Reply"
						},
						color: "#080808",
						color_hover: "#34345c"
					},
					target: {
						text: {
							left: "[",
							right: "]",
							color: "#5c6f85"
						},
						color: "#34345c",
						color_hover: "#dd0000",
						padding: {
							inner: 3,
							outer: 6
						}
					},
					submit: {
						text: "Post",
						text_active: "Posting",
						text_retry: "Retrying"
					},
					status: {
						text_color: "#34345c",
						font: {
							size: "10px"
						},
						spacing: 4,
						padding: {
							top: 4,
							bottom: 0
						},
						bar_height: 4,
						"default": {
							color: "#b7c5d9"
						},
						context_acquire: {
							color: "#117743"
						},
						cooldown: {
							color: "#f8f8f8"
						},
						upload: {
							color: "#dd0000"
						},
						download: {
							color: "#3191b7"
						},
						parse: {
							color: "#9131b7"
						}
					},
					cooldown: {
						opacity: 1,
						colors: {
							bg: "#d6daf0",
							bar: "#f8f8f8"
						},
						transition: {
							time: 0.5,
							method: "linear",
							delay: 0
						}
					}
				};

				this.messages = {
					size: {
						width: 480
					},
					colors: {
						plain: {
							text: "#080808",
							title_shadow: "#c0c0c0",
							bg: "#f8f8f8",
							link: "#080808",
							link_hover: "#dd0000"
						},
						error: {
							text: "#f8f8f8",
							title_shadow: "#080808",
							bg: "#e72d00",
							link: "#f8f8f8",
							link_hover: "#008be7"
						},
						good: {
							text: "#f8f8f8",
							title_shadow: "#080808",
							bg: "#008be7",
							link: "#f8f8f8",
							link_hover: "#e72d00"
						},
						okay: {
							text: "#f8f8f8",
							title_shadow: "#080808",
							bg: "#00ac04",
							link: "#f8f8f8",
							link_hover: "#f6c500"
						},
						info: {
							text: "#080808",
							title_shadow: "#f8f8f8",
							bg: "#f6c500",
							link: "#080808",
							link_hover: "#a900e7"
						},
						parse: {
							text: "#f8f8f8",
							title_shadow: "#080808",
							bg: "#a900e7",
							link: "#f8f8f8",
							link_hover: "#f6c500"
						}
					},
					border: {
						radius: 6
					},
					padding: {
						left: 4,
						top: 4,
						right: 4,
						bottom: 4
					},
					bg: {
						opacity: 0.875,
						shadow: {
							color: "#d6daf0",
							offset: {
								left: 0,
								top: 0
							},
							blur: 4,
							size: 2
						},
						shadow_min: {
							color: "#d6daf0",
							offset: {
								left: 0,
								top: 0
							},
							blur: 2,
							size: 1
						}
					},
					body: {
						font: {}
					},
					title: {
						shadow: {
							offset: {
								left: 0,
								top: 0
							},
							blur: 3
						},
						separation: 2,
						font: {
							weight: "bold"
						}
					},
					timer: {
						font: {
							size: "8px"
						},
						opacity: 0.75
					},
					link: {
						font: { weight: "bold", decoration: "underline" }
					},
					progress_bar: {
						separation: 4,
						height: 4,
						colors: {
							"default": "#f8f8f8",
							active: "#3191b7"
						}
					}
				};

				this.board_info = {
					title: {
						color: "#af0a0f",
						font: {
							family: "tahoma",
							size: "32px",
							weight: "bold",
							spacing: "-2px"
						}
					},
					subtitle: {
						color: "#af0a0f",
						font: {
							family: "tahoma",
							size: "12px"
						}
					},
					banner: {
						size: {
							width: 300,
							height: 100
						},
						padding: {
							bottom: 10
						}
					},
					padding: {
						bottom: 10
					}
				};

				this.updater = {
					symbols: {
						added: "+",
						removed: "-",
						post: "p",
						image: "i",
						separators: {
							post_image: ",",
							added_removed: ";"
						},
						countdown: "s"
					},
					colors: {
						text: "#080808",
						link: "#0f0c5c",
						link_hover: "#dd0000",
						added: "#117743",
						removed: "#ff0000",
						limit: "#dd0000"
					},
					small: {
						endings: {
							start: "[",
							end: "]",
							spacing: 4,
							color: "#5c6f85"
						},
						padding: 2,
						spacing: 8,
						progress: {
							colors: {
								ajax: "#3191b7",
								parse: "#9131b7"
							},
							opacity: 0.25
						}
					},
					bg: {
						color: "#d6daf0",
						opacity: 0.75,
						radius: 4,
						shadow: {
							color: "#34345c",
							offset: {
								left: 0,
								top: 0
							},
							blur: 2,
							size: 1
						}
					}
				};

				this.settings = {
					font: {
						size: "14px"
					},
					tabs: {
						spacing: {
							outer: 4,
							inner: 4
						},
						bg: {
							0: "#ffffff",
							100: "rgba(255,255,255,0)",
							direction: "t2b"
						},
						border_outer: {
							size: 1,
							style: "solid",
							color: "#34345c",
							color_inactive: "#5c6f85",
							radius: 6
						},
						title: {
							font: {},
							shadow: {
								offset: {
									left: 0,
									top: 0
								},
								color: "#f8f8f8",
								blur: 2,
								factor: 3
							},
							current: {
								shadow: {
									offset: {
										left: 0,
										top: 0
									},
									color: "#d6daf0",
									blur: 2,
									factor: 3
								}
							}
						},
						mode_select: {
							color: "#5c6f85",
							color_hover: "#34345c"
						}
					},
					inputs: {
						text_color: {
							normal: "#5c6f85",
							hover: "#080808"
						},
						textbox: {
							width: 100
						},
						checkbox: {
							separation: {
								inner: 8,
								outer: 8
							},
							colors: {
								non_current: "#34345c"
							}
						},
						slider: {
							width: 100,
							height: 16,
							grabber_size: 4,
							border: {
								color: "#34345c",
								size: 1,
								style: "solid",
								radius: 4
							},
							line_size: 1,
							colors: {
								line: {
									top: "#5c6f85",
									bottom: "#f8f8f8"
								},
								grabber: {
									bg: "#f8f8f8"
								}
							}
						}
					},
					section: {
						separator: {
							padding: {
								left: 6,
								top: 0,
								right: 6,
								bottom: 10
							},
							colors: {
								top: "#5c6f85",
								bottom: "#f8f8f8"
							},
							style: "solid",
							size: 1
						},
						label: {
							font: {
								size: "16px",
								weight: "bold"
							}
						},
						padding: {
							left: 12,
							right: 12,
							top: 10,
							bottom: 10
						},
						change_indicator: {
							color: "#dd0000",
							text: "*"
						},
						item: {
							label: {
								font: {
									weight: "bold"
								}
							},
							colors: {
								bg_odd: "rgba(255,255,255,0.25)",
								bg_hover: "rgba(255,255,255,0.5)"
							},
							hover_shadow: {
								color: "rgba(255,255,255,0.5)",
								offset: {
									left: 0,
									top: 0
								},
								blur: 4,
								size: 2
							},
							padding: 2
						}
					}
				};

				// Insert stylesheet
				head.append( //{ Stylesheet
					(this.style_tag = $(document.createElement("style")))
					.attr("id", "xch_style")
					.html(this_private.parse_stylesheet.call(this, this.stylesheet_base =
						/*!stylesheet-pre-parse!*/this_private.parse_stylesheet_pre.call(this,
						/*!stylesheet-start!*/
						//{ Body
						"body${background-color:<<body.colors.bg>>;background-image:none;margin:0px;padding:0px;border:0px hidden;overflow-x:auto;overflow-y:scroll;<<!font:body.font>>}\n" +
						"$.top_gradient{position:fixed;left:0;top:0;right:0;z-index:-100;height:<<top_gradient.height>>;<<!gradient:top_gradient>>}\n" +
						//}

						//{ Threads
						"$.thread+$.thread{margin-top:<<thread.separator.height>>px;padding-top:<<thread.separator.size>>px;border-top:<<thread.separator.size>>px <<thread.separator.style>> <<thread.separator.color>>;}\n" +
						"$.thread{padding:<<thread.padding.top>>px <<thread.padding.right>>px <<thread.padding.bottom>>px <<thread.padding.left>>px;<<css.box-sizing>>:border-box;}\n" +
						//}

						//{ Post
						"$.post_container+$.post_container{margin-top:<<post.separation>>px;}\n" +
						"$.post_container{display:block;max-width:100%;margin:0px;padding:0px;background-color:transparent;border:0px hidden;max-width:none;position:relative;}\n" +
						"$.post_container.op>$.post{display:block;}\n" +
						"$.post_container.reply>$.post{background-color:<<post.colors.bg>>;border-width:<<post.border.top>>px <<post.border.right>>px <<post.border.bottom>>px <<post.border.left>>px;border-style:<<post.border.style>>;border-color:<<post.colors.border>>;<<css.border-radius>>:<<post.border.radius.topleft>>px <<post.border.radius.topright>>px <<post.border.radius.bottomright>>px <<post.border.radius.bottomleft>>px;}\n" +
						"$.post_container.reply.highlighted>$.post{background-color:<<post.colors.bg_hl>>;border-color:<<post.colors.border_hl>>;}\n" +
						"$.post_container.reply.highlighted{background-color:transparent;}\n" +
						"$.post_container.op.focused>$.post{}\n" +
						"$.post_container.reply.focused:not(.inline)>$.post{box-shadow:<<!shadow:post.focus.normal.shadow>>;z-index:1;}\n" +
						"$.post_container.reply.focused.inline>$.post{box-shadow:<<!shadow:post.focus.inline.shadow>>;z-index:1;}\n" +

						"$.post{max-width:100%;display:inline-block;margin:0px;padding:0px;color:<<post.colors.text>>;<<!font:post.font>>}\n" +
						//}

						//{ Post header progress
						"$.post_header_progress{background-color:transparent;position:absolute;left:0;right:0;bottom:0;height:<<post.load_bar.height>>px;}\n" +
						"$.post_header_progress.ajax{background-color:<<post.load_bar.colors.ajax>>;}\n" +
						"$.post_header_progress.parse{background-color:<<post.load_bar.colors.parse>>;}\n" +
						"$.post_header_progress_inner{position:absolute;left:0;width:0px;bottom:0;top:0;}\n" +
						"$.post_header_progress_inner.ajax{background-color:<<post.load_bar.colors.ajax>>;}\n" +
						"$.post_header_progress_inner.parse{background-color:<<post.load_bar.colors.parse>>;}\n" +
						//}

						//{ Post header
						"$.post_header{position:relative;display:block;<<!font:post.header.font>>}\n" +
						"$.post_container.op>$.post>$.post_header{padding:0px <<post.header.padding.right>>px 0px 0px;}\n" +
						"$.post_container.reply>$.post>$.post_header{padding:<<post.header.padding.top>>px <<post.header.padding.right>>px <<post.header.padding.bottom>>px <<post.header.padding.left>>px;background-color:<<post.header.colors.bg>>;border-width:<<post.header.border.top>>px <<post.header.border.right>>px <<post.header.border.bottom>>px <<post.header.border.left>>px;border-style:<<post.header.border.style>>;border-color:<<post.header.colors.border>>;<<css.border-radius>>:<<post.border.radius.topleft>>px <<post.border.radius.topright>>px 0px 0px;}\n" +
						"$.post_container.reply.highlighted>$.post>$.post_header{background-color:<<post.header.colors.bg_hl>>;border-color:<<post.header.colors.border_hl>>;}\n" +
						"$.post_header>${vertical-align:middle;}" +
						"$.post_header>$+$:not(.no_spacing){margin-left:<<post.header.spacing>>px;}\n" +
						//}

						//{ Post subject
						"$.post_subject{word-wrap:break-word;color:<<post.header.subject.color>>;<<!font:post.header.subject.font>>}\n" +
						//}

						//{ Post header deleted indicators
						"$.post_indicator_deleted_image{color:<<post.deleted_indicator.color>>;<<!font:post.deleted_indicator.font>>}\n" +
						"$.post_indicator_deleted_image:after{content:\"<<!escape_string:post.deleted_indicator.image_text>>\";}\n" +
						"$.post_indicator_deleted_post{color:<<post.deleted_indicator.color>>;<<!font:post.deleted_indicator.font>>}\n" +
						"$.post_indicator_deleted_post:after{content:\"<<!escape_string:post.deleted_indicator.post_text>>\";}\n" +
						//}

						//{ Name
						"$.post_identity{color:<<post.header.identity.name.colors.user>>;text-decoration:none;}\n" +
						"$.post_identity[href]{color:<<post.header.identity.email_colors.text>>;text-decoration:underline;}\n" +
						"$.post_identity[href]:hover{color:<<post.header.identity.email_colors.text_hover>>;}\n" +
						"$.post_identity[href]>$.post_identity_name{color:<<post.header.identity.email_colors.text>>;}\n" +
						"$.post_identity[href]:hover>$.post_identity_name{color:<<post.header.identity.email_colors.text_hover>>;}\n" +
						"$.post_identity_name{word-wrap:break-word;<<!font:post.header.identity.name.font>>;}\n" +
						"$.post_identity_tripcode{margin-left:0px;<<!font:post.header.identity.tripcode.font>>}\n" +
						"$.post_identity_capcode," +
						"$.post_identity_capcode_indicator{<<!font:post.header.identity.capcode.font>>}\n" +
						"$.post_identity_name," +
						"$.post_identity_tripcode," +
						"$.post_identity_capcode{color:<<post.header.identity.name.colors.user>>;}\n" +
						"$.post_identity.mod>$.post_identity_name," +
						"$.post_identity.mod>$.post_identity_tripcode," +
						"$.post_identity.mod>$.post_identity_capcode," +
						"$.post_identity.mod>$.post_identity_capcode_indicator{color:<<post.header.identity.name.colors.mod>>;}\n" +
						"$.post_identity.admin>$.post_identity_name," +
						"$.post_identity.admin>$.post_identity_tripcode," +
						"$.post_identity.admin>$.post_identity_capcode," +
						"$.post_identity.admin>$.post_identity_capcode_indicator{color:<<post.header.identity.name.colors.admin>>;}\n" +
						"$.post_identity_capcode_indicator{padding:0px <<post.header.identity.spacing>>px;}\n" +
						//}

						//{ Time
						"$.post_time{white-space:nowrap;<<!font:post.header.time.font>>}\n" +
						//}

						//{ Post id
						"$.post_no," +
						"$.post_number{text-decoration:none;color:<<post.header.number.colors.text>>;<<!font:post.header.number.font>>}\n" +
						"$.post_no:hover," +
						"$.post_number:hover{color:<<post.header.number.colors.text_hover>>;}\n" +
						"$.post_no:before{content:\" \";width:0px;display:inline-block;overflow:hidden;}\n" +
						//}

						//{ Icons
						"$.post_icon{font-family:FontAwesome;text-decoration:none;display:inline-block;vertical-align:middle;color:<<post.header.icons.color>>;}\n" +
						"$.post_icon.sticky:after{content:\"\\f08d\";}\n" +
						"$.post_icon.locked:after{content:\"\\f023\";}\n" +
						//}

						//{ Post header options
						"$.post_option{color:<<post.header.options.colors.text>>;<<!font:post.header.options.font>>}\n" +
						"$.post_option:before{content:\"<<!escape_string:post.header.options.separator.start>>\";}\n" +
						"$.post_option:after{content:\"<<!escape_string:post.header.options.separator.end>>\";}\n" +
						"$.post_option_text{text-decoration:none;padding:0px <<post.header.options.spacing.inner>>px 0px <<post.header.options.spacing.inner>>px;color:<<post.header.options.colors.link>>;}\n" +
						"$.post_option_text:hover{color:<<post.header.options.colors.link_hover>>;}\n" +
						"$.post_option.reply_to>$.post_option_text:before{content:\"<<!escape_string:post.header.options.text.reply_to>>\";}\n" +
						//}

						//{ Post header backlinks
						"$.post_backlinks{<<!font:post.header.backlinks.font>>}\n" +
						"$.post_footer>$.post_backlinks{<<!font:post.footer.backlinks.font>>}\n" +
						"$.post_backlinks.empty{display:none;}\n" +
						"$.post_backlinks:not(.empty):before{content:\"<<!escape_string:post.header.backlinks.label>>\";}\n" +
						"$.post_header>$.post_backlinks>$.post_backlink:first-child{margin-left:<<post.header.backlinks.spacing_first>>px;}\n" +
						//}

						//{ Backlinks
						"$.post_backlink{color:<<post.header.backlinks.colors.text>>;text-decoration:none;}\n" +
						"$.post_backlink:hover{color:<<post.header.backlinks.colors.text_hover>>;}\n" +
						"$.post_backlink.open{color:<<post.header.backlinks.colors.text_open>>;}\n" +
						"$.post_backlink.open:hover{color:<<post.header.backlinks.colors.text_open_hover>>;}\n" +
						"$.post_backlink.highlighted{border-width:0px 0px 1px 0px;margin-bottom:-1px;border-style:dashed;border-color:<<post.header.backlinks.colors.text_hl>>;color:<<post.header.backlinks.colors.text_hl>>;}\n" +
						"$.post_backlink:before{content:\" \";width:0px;display:inline-block;overflow:hidden;}\n" +
						"$.post_backlink+$.post_backlink{margin-left:<<post.header.backlinks.spacing>>px;}\n" +

						"$.post_footer>$.post_backlink{color:<<post.footer.backlinks.colors.text>>;text-decoration:none;}\n" +
						"$.post_footer>$.post_backlink:hover{color:<<post.footer.backlinks.colors.text_hover>>;}\n" +
						"$.post_footer>$.post_backlink.open{color:<<post.footer.backlinks.colors.text_open>>;}\n" +
						"$.post_footer>$.post_backlink.open:hover{color:<<post.footer.backlinks.colors.text_open_hover>>;}\n" +
						"$.post_footer>$.post_backlink.highlighted{border-width:0px 0px 1px 0px;margin-bottom:-1px;border-style:dashed;border-color:<<post.footer.backlinks.colors.text_hl>>;color:<<post.footer.backlinks.colors.text_hl>>;}\n" +
						"$.post_footer>$.post_backlink:before{content:\" \";width:0px;display:inline-block;overflow:hidden;}\n" +
						"$.post_footer>$.post_backlink+$.post_backlink{margin-left:<<post.footer.backlinks.spacing>>px;}\n" +
						//}

						//{ Post header settings link
						"$.post_settings{color:<<post.header.options.colors.link>>;cursor:pointer;<<css.transition>>:opacity <<post.header.settings.opacity.transition.time>>s <<post.header.settings.opacity.transition.method>> <<post.header.settings.opacity.transition.delay>>s;opacity:<<post.header.settings.opacity.normal>>;}\n" +
						"$.post:hover>$.post_header>$.post_settings{opacity:<<post.header.settings.opacity.hover>>;}\n" +
						"$.post_settings:hover{color:<<post.header.options.colors.link_hover>>;}\n" +
						"$.post_settings:after{content:\"<<!escape_string:symbols.triangles.down>>\";}\n" +
						//}

						//{ Post image
						"$.post_image_container{margin:0px;padding:<<post.comment.padding.top>>px <<post.comment.padding.left>>px <<post.comment.padding.bottom>>px <<post.image.padding.left>>px;display:inline-block;float:left;}\n" +
						"$.post_image_container.embed{margin:0px;padding-bottom:0px;display:block;float:none;}\n" +

						"$.post_image_link{padding:0px;margin:0px;border:0px hidden;display:inline-block;position:relative;background-color:<<post.image.reply.bg>>;}\n" +
						"$.post_image_link>$.post_image{padding:0px;margin:0px;display:block;float:none;box-shadow:<<!shadow:post.image.reply.thumbnail.shadow>>;}\n" +
						"$.post_image_container.embed>$.post_image_link>$.post_image:not(.full){display:none;}\n" +
						"$.post_image_container.embed>$.post_image_link>$.post_image.full{position:relative;box-shadow:<<!shadow:post.image.reply.full.shadow>>;}\n" +
						"$.post_image_container.embed>$.post_image_link>$.post_image_thumbnail{display:block;position:absolute;left:0;top:0;bottom:0;right:0;}\n" +
						"$.post_image_container.embed.loaded>$.post_image_link>$.post_image_thumbnail{display:none;}\n" +

						"$.post_container.op>$.post>$.post_image_container{padding:0px <<post.comment.padding.left>>px 0px 0px;}\n" +
						"$.post_container.op>$.post>$.post_image_container>$.post_image_link{background-color:<<post.image.op.bg>>;}\n" +
						"$.post_container.op>$.post>$.post_image_container>$.post_image_link>$.post_image{box-shadow:<<!shadow:post.image.op.thumbnail.shadow>>;}\n" +
						"$.post_container.op>$.post>$.post_image_container.embed>$.post_image_link>$.post_image.full{box-shadow:<<!shadow:post.image.op.full.shadow>>;}\n" +
						//}

						//{ File info
						"$.post_file_info{padding:<<post.file_info.padding.top>>px <<post.file_info.padding.right>>px <<post.file_info.padding.bottom>>px <<post.file_info.padding.left>>px;margin:0px 0px -<<post.comment.padding.top>>px 0px;<<!font:post.file_info.font>>}\n" +
						"$.post_container.op>$.post>$.post_file_info{padding:0px 0px <<post.file_info.padding.bottom>>px 0px;margin:0px;}\n" +
						"$.post_file_info_file{text-decoration:none;<<!font:post.file_info.file_name.font>>}\n" +
						"$.post_file_info_attributes{<<!font:post.file_info.attributes.font>>}\n" +
						"$.post_file_info_attributes:before{content:\"<<!escape_string:post.file_info.attributes.prefix>>\";}\n" +
						"$.post_file_info_attributes:after{content:\"<<!escape_string:post.file_info.attributes.suffix>>\";}\n" +
						"$.post_file_info>a+a," +
						"$.post_file_info>a+span," +
						"$.post_file_info>span+a," +
						"$.post_file_info>span+span{margin-left:<<post.file_info.spacing>>px;}\n" +

						"$.post_file_info_extra_links{<<!font:post.file_info.file_extras.font>>}\n" +
						"$.post_file_info_extra_link{text-decoration:none;}\n" +
						"$.post_file_info_extra_link+$.post_file_info_extra_link{margin-left:<<post.file_info.file_extras.spacing>>px;}\n" +
						//}

						//{ Comment
						"$.post_body," +
						"$.post_body_full{word-wrap:break-word;color:<<post.comment.colors.text>>;display:block;margin:0px;padding:<<post.comment.padding.top>>px <<post.comment.padding.right>>px <<post.comment.padding.bottom>>px <<post.comment.padding.left>>px;<<!font:post.comment.font>>}\n" +
						"$.post_container.op>$.post>$.post_body," +
						"$.post_container.op>$.post>$.post_body_full{padding:<<post.comment.padding.top>>px <<post.comment.padding.right>>px <<post.comment.padding.bottom>>px 0px;}\n" +
						"$.post_body.full," +
						"$.post_body:not(.full)+$.post_body_full{display:none;}\n" +
						"$.post_body_after{display:block;clear:both;height:0px;margin:0px;padding:0px;}" +
						//}

						//{ Notifications
						"$.post_notifications{text-indent:<<post.notifications.spacing.outer>>px;margin-left:-<<post.notifications.spacing.outer>>px;}\n" +
						"$.post_notifications.empty{display:none;}\n" +
						"$.post_container.reply>$.post>$.post_notifications{padding:<<post.notifications.padding.top>>px <<post.comment.padding.right>>px <<post.comment.padding.bottom>>px <<post.comment.padding.left>>px;margin-top:-<<post.comment.padding.bottom>>px;}\n" +

						"$.post_notification{vertical-align:middle;position:relative;text-indent:0px;display:inline-block;color:<<post.notifications.colors.text>>;<<!font:post.notifications.font>>}\n" +
						"$.post_notification:before{vertical-align:middle;content:\"<<!escape_string:post.notifications.separator.start>>\";}\n" +
						"$.post_notification:after{vertical-align:middle;content:\"<<!escape_string:post.notifications.separator.end>>\";}\n" +
						"$.post_notification_message{vertical-align:middle;position:relative;text-decoration:none;padding:0px <<post.notifications.spacing.inner>>px 0px <<post.notifications.spacing.inner>>px;color:<<post.notifications.colors.link>>;}\n" +
						"$.post_notification_message:hover{color:<<post.notifications.colors.link_hover>>;}\n" +

						"$.post_notification_message>$.post_notification_text{position:relative;vertical-align:baseline;}\n" +
						"$.post_notification.truncated>$.post_notification_message>$.post_notification_text:before{content:\"<<!escape_string:post.notifications.text.too_long>>\"}\n" +
						"$.post_notification.truncated.expand>$.post_notification_message>$.post_notification_text:before{content:\"<<!escape_string:post.notifications.text.too_long_expand>>\"}\n" +
						"$.post_notification.truncated.shrink>$.post_notification_message>$.post_notification_text:before{content:\"<<!escape_string:post.notifications.text.too_long_shrink>>\"}\n" +
						"$.post_notification.omitted>$.post_notification_message>$.post_notification_text:before{content:\"<<!escape_string:post.notifications.text.omitted_hidden_prefix>>\" attr(omitted_value) \"<<!escape_string:post.notifications.text.omitted_hidden_suffix>>\";}\n" +
						"$.post_notification.omitted.shrink>$.post_notification_message>$.post_notification_text:before{content:\"<<!escape_string:post.notifications.text.omitted_visible_prefix>>\" attr(omitted_value) \"<<!escape_string:post.notifications.text.omitted_visible_suffix>>\";}\n" +

						"$.post_notification.loaded>$.post_notification_message>$.post_notification_progress{display:none;}\n" +
						"$.post_notification_progress{position:absolute;left:0px;top:0;right:0px;bottom:0;opacity:<<post.notifications.progress.opacity>>;}\n" +
						"$.post_notification_progress.ajax{background-color:<<post.notifications.progress.colors.ajax>>;}\n" +
						"$.post_notification_progress.parse{background-color:<<post.notifications.progress.colors.parse>>;}\n" +
						"$.post_notification_progress_inner{position:absolute;left:0;top:0;width:0px;bottom:0;}\n" +
						"$.post_notification_progress_inner.ajax{background-color:<<post.notifications.progress.colors.ajax>>;}\n" +
						"$.post_notification_progress_inner.parse{background-color:<<post.notifications.progress.colors.parse>>;}\n" +
						"$.post_notification+$.post_notification{padding-left:<<post.notifications.spacing.outer>>px;}\n" +
						//}

						//{ Footer
						"$.post_footer{clear:both;display:block;margin:0px <<post.footer.padding.right_outer>>px <<post.footer.padding.bottom>>px <<post.footer.padding.left_outer>>px;border-width:<<post.footer.border.size>>px 0px 0px 0px;border-style:<<post.footer.border.style>>;padding:<<post.footer.padding.top_inner>>px <<post.footer.padding.right_inner>>px 0px <<post.footer.padding.left_inner>>px;<<!font:post.footer.font>>}\n" +
						"$.post_container.op>$.post>$.post_footer{margin-left:0px;margin-right:0px;padding-left:0px;padding-right:0px;border-color:<<post.footer.border.colors.op>>;}\n" +
						"$.post_notifications:not(.empty)+$.post_footer{margin-top:<<post.footer.padding.top_outer>>px;}\n" +
						"$.post_container.op.highlighted>$.post>$.post_footer{border-color:<<post.footer.border.colors.reply>>;}\n" +
						"$.post_container.reply>$.post>$.post_footer{border-color:<<post.footer.border.colors.reply_hl>>;}\n" +
						"$.post_container.reply.highlighted>$.post>$.post_footer{border-color:<<post.footer.border.colors.reply_hl>>;}\n" +
						"$.post_footer.empty," +
						"$.post_footer.hidden{display:none;}\n" +
						"$.post_footer>$.post_backlinks:not(.empty):before{content:\"<<!escape_string:post.footer.backlinks.label>>\";}\n" +
						"$.post_footer>$.post_backlinks>$.post_backlink:first-child{margin-left:<<post.footer.backlinks.spacing_first>>px;}\n" +
						//}

						//{ Backlink/reference targets
						"$.post_backlink[to_target]:after{white-space:nowrap;content:\"<<!escape_string:post.backlink_indicator.suffix_first>>\" attr(to_target) \"<<!escape_string:post.backlink_indicator.suffix_last>>\";}\n" +
						"$.post_reference[to_target]:after{white-space:nowrap;content:\"<<!escape_string:post.reference_indicator.suffix_first>>\" attr(to_target) \"<<!escape_string:post.reference_indicator.suffix_last>>\";}\n" +
						//}

						//{ Comment stuff
						"$.spoiler{color:<<post.comment.colors.spoiler.text>>;background-color:<<post.comment.colors.spoiler.bg>>;}\n" +
						"$.spoiler:hover{color:<<post.comment.colors.spoiler.text_hover>>;background-color:<<post.comment.colors.spoiler.bg_hover>>;}\n" +
						"$.quote{color:<<post.comment.colors.quote.text>>;}\n" +
						"$.quote:hover{color:<<post.comment.colors.quote.text_hover>>;}\n" +
						"$.post_link{color:<<post.comment.colors.link.text>>;}\n" +
						"$.post_link:hover{color:<<post.comment.colors.link.text_hover>>;}\n" +
						//}

						//{ Comment references
						"$.post_reference{color:<<post.comment.colors.reply_link.text>>;text-decoration:none;}\n" +
						"$.post_reference:hover{color:<<post.comment.colors.reply_link.text_hover>>;}\n" +
						"$.post_reference.open{color:<<post.comment.colors.reply_link.text_open>>;}\n" +
						"$.post_reference.open:hover{color:<<post.comment.colors.reply_link.text_open_hover>>;}\n" +
						"$.post_reference+$.post_container.inline{display:table;}\n" +
						"$.post_reference+$.post_container.inline+br{display:none;}\n" +
						"$.post_reference.highlighted{border-width:0px 0px 1px 0px;margin-bottom:-1px;border-style:dashed;border-color:<<post.comment.colors.reply_link.text_hl>>;color:<<post.comment.colors.reply_link.text_hl>>;}\n" +
						//}

						//{ Floating inline
						"$.floating_inline_container{position:absolute;left:0;top:0;right:0;margin:0px;padding:0px;}\n" +
						"$.post_container.floating{position:absolute;z-index:120;display:inline-block;}\n" +
						"$.post_container.floating>$.post{box-shadow:<<!shadow:post.inline.floating.shadow>>;}\n" +
						//}

						//{ Inlining
						"$.post_embedded_backlink_container.empty{display:none;}\n" +
						"$.post_embedded_backlink_container{padding:<<post.inline.container_padding.top>>px <<post.inline.container_padding.right>>px <<post.inline.container_padding.bottom>>px <<post.inline.container_padding.left>>px;}\n" +
						"$.post_embedded_backlink_container>$.post_container.inline{display:table;}\n" +
						"$.post_container.inline>$.post{box-shadow:<<!shadow:post.inline.embedded.shadow>>;}\n" +
						//}

						//{ Image preview
						"$.image_preview{}\n" +
						"$.image_preview_inner_1{position:fixed;z-index:200;display:block;}\n" +
						"$.image_preview_inner_2{position:relative;width:100%;height:100%;background-color:<<image_preview.colors.bg>>;box-shadow:<<!shadow:image_preview.shadow>>;}\n" +
						"$.image_preview_inner_3{position:relative;width:100%;height:100%;overflow:hidden;}\n" +
						"$.image_preview_origin{position:absolute;left:50%;top:50%;}\n" +
						"$.image_preview_size{position:absolute;left:0;top:0;}\n" +
						"$.image_preview_image_container{position:absolute;display:block;background-color:transparent;left:-50%;top:-50%;width:100%;height:100%;}\n" +
						"$.image_preview_thumbnail{display:block;background-position:center center;background-repeat:no-repeat;background-color:transparent;opacity:<<image_preview.thumbnail.opacity>>;position:absolute;left:0;top:0;bottom:0;right:0;}\n" +
						"$.image_preview_thumbnail.hidden{display:none;}\n" +
						"$.image_preview_thumbnail.main{display:block;opacity:1;}\n" +
						"$.image_preview_image{display:block;float:none;margin:0px;padding:0px;border:0px;position:absolute;left:0;top:0;width:100%;height:100%;}\n" +
						"$.image_preview_image.hidden{display:none;}\n" +
						"$.image_preview_overlay{display:block;background-color:transparent;position:absolute;left:0;top:0;bottom:0;right:0;}\n" +
						"$.image_preview_info{position:absolute;left:0;right:0;bottom:100%;padding:<<image_preview.header.padding.top>>px <<image_preview.header.padding.right>>px <<image_preview.header.padding.bottom>>px <<image_preview.header.padding.left>>px;color:<<image_preview.header.color>>;text-shadow:<<!shadow:image_preview.header.shadow>>;}\n" +
						"$.image_preview_info_item.error{color:<<image_preview.colors.error>>;}\n" +
						"$.image_preview_info_item+$.image_preview_info_item:before{content:\"<<!escape_string:image_preview.header.separator>>\"}\n" +
						"$.image_preview_connector{position:absolute;z-index:19;display:block;background-color:transparent;}\n" +
						//}

						//{ Checkboxes
						'$.checkbox{display:inline-block;line-height:0;cursor:pointer;}\n' +
						'$.checkbox_indicator{vertical-align:middle;display:inline-block;line-height:0;background-color:<<checkbox_settings.colors.bg>>;border-style:solid;border-color:<<checkbox_settings.colors.border>>;overflow:hidden;cursor:pointer;vertical-align:middle;margin:0px;padding:0px;<<css.box-sizing>>:border-box;}\n' +
						'$.checkbox_indicator:hover{border-color:<<checkbox_settings.colors.border_hover>>;background-color:<<checkbox_settings.colors.bg_hover>>;}\n' +
						'$.checkbox_svg{fill:<<checkbox_settings.colors.check>>;opacity:1;margin:0px;padding:0px;display:block;}\n' +
						'$.checkbox_indicator:hover>$.checkbox_svg{fill:<<checkbox_settings.colors.check_hover>>;}\n' +
						'$.checkbox_input{display:none;}\n' +
						'$.checkbox_input:not(:checked)+$.checkbox_indicator>$.checkbox_svg{opacity:0;}\n' +

						'$.checkbox.tiny>$.checkbox_indicator{width:<<checkbox_settings.sizes.tiny.box_size>>px;height:<<checkbox_settings.sizes.tiny.box_size>>px;<<css.border-radius>>:<<checkbox_settings.sizes.tiny.border_radius>>px;border-width:<<checkbox_settings.sizes.tiny.border_width>>px;}\n' +
						'$.checkbox.tiny>$.checkbox_indicator>$.checkbox_svg{width:<<checkbox_settings.sizes.tiny.box_size>>px;height:<<checkbox_settings.sizes.tiny.box_size>>px;}\n' +
						'$.checkbox.small>$.checkbox_indicator{width:<<checkbox_settings.sizes.small.box_size>>px;height:<<checkbox_settings.sizes.small.box_size>>px;<<css.border-radius>>:<<checkbox_settings.sizes.small.border_radius>>px;border-width:<<checkbox_settings.sizes.small.border_width>>px;}\n' +
						'$.checkbox.small>$.checkbox_indicator>$.checkbox_svg{width:<<checkbox_settings.sizes.small.box_size>>px;height:<<checkbox_settings.sizes.small.box_size>>px;}\n' +
						'$.checkbox.normal>$.checkbox_indicator{width:<<checkbox_settings.sizes.normal.box_size>>px;height:<<checkbox_settings.sizes.normal.box_size>>px;<<css.border-radius>>:<<checkbox_settings.sizes.normal.border_radius>>px;border-width:<<checkbox_settings.sizes.normal.border_width>>px;}\n' +
						'$.checkbox.normal>$.checkbox_indicator>$.checkbox_svg{width:100%;height:100%;}\n' +
						//}

						//{ Radioboxes
						'$.radiobox{display:inline-block;line-height:0;cursor:pointer;}\n' +
						'$.radiobox_indicator{vertical-align:middle;display:inline-block;line-height:0;overflow:hidden;cursor:pointer;vertical-align:middle;margin:0px;padding:0px;<<css.box-sizing>>:border-box;}\n' +
						'$.radiobox_svg{opacity:1;margin:0px;padding:0px;display:block;}\n' +
						'$.radiobox_input{display:none;}\n' +
						'$.radiobox_input:not(:checked)+$.radiobox_indicator>$.radiobox_svg>$.radiobox_svg_g>$.radiobox_svg_tick{opacity:0;}\n' +
						'$.radiobox_svg_bg{fill:<<checkbox_settings.colors.bg>>;}\n' +
						'$.radiobox_svg_border{fill:<<checkbox_settings.colors.border>>;}\n' +
						'$.radiobox_svg_tick{fill:<<checkbox_settings.colors.check>>;}\n' +
						'$.radiobox_indicator:hover>$.radiobox_svg>$.radiobox_svg_g>$.radiobox_svg_bg{fill:<<checkbox_settings.colors.bg_hover>>;}\n' +
						'$.radiobox_indicator:hover>$.radiobox_svg>$.radiobox_svg_g>$.radiobox_svg_border{fill:<<checkbox_settings.colors.border_hover>>;}\n' +
						'$.radiobox_indicator:hover>$.radiobox_svg>$.radiobox_svg_g>$.radiobox_svg_tick{fill:<<checkbox_settings.colors.check_hover>>;}\n' +

						'$.radiobox.tiny>$.radiobox_indicator{width:<<checkbox_settings.sizes.tiny.box_size>>px;height:<<checkbox_settings.sizes.tiny.box_size>>px;}\n' +
						'$.radiobox.tiny>$.radiobox_indicator>$.radiobox_svg{width:<<checkbox_settings.sizes.tiny.box_size>>px;height:<<checkbox_settings.sizes.tiny.box_size>>px;}\n' +
						'$.radiobox.small>$.radiobox_indicator{width:<<checkbox_settings.sizes.small.box_size>>px;height:<<checkbox_settings.sizes.small.box_size>>px;}\n' +
						'$.radiobox.small>$.radiobox_indicator>$.radiobox_svg{width:<<checkbox_settings.sizes.small.box_size>>px;height:<<checkbox_settings.sizes.small.box_size>>px;}\n' +
						'$.radiobox.normal>$.radiobox_indicator{width:<<checkbox_settings.sizes.normal.box_size>>px;height:<<checkbox_settings.sizes.normal.box_size>>px;}\n' +
						'$.radiobox.normal>$.radiobox_indicator>$.radiobox_svg{width:100%;height:100%;}\n' +
						//}

						//{ Content padding
						"$.content_padding.top{display:block;height:<<body.content_padding.top>>px;}\n" +
						"$.content_padding.bottom{display:block;height:<<body.content_padding.bottom>>px;}\n" +
						//}

						//{ Header
						"$.header{position:absolute;left:0;top:0;right:0;z-index:100;<<!font:header.font>>}\n" +
						"$.header:after{clear:both;}\n" +
						"$.header.fixed{position:fixed;}\n" +
						"$.header_background{position:absolute;left:0;top:0;bottom:0;right:0;opacity:<<header.background.opacity>>;box-shadow:<<!shadow:header.background.shadow>>;<<!gradient:header.background.gradient>>}\n" +
						"$.header_content{display:block;position:relative;padding:<<header.padding>>px;}\n" +

						"$.header_boards{display:inline;text-align:left;color:<<header.boards.colors.group>>;}\n" +
						"$.header_board_group{color:<<header.boards.colors.separator>>;}\n" +
						"$.header_board_group:before{content:\"<<!escape_string:header.separators.group_before>>\";}\n" +
						"$.header_board_group:after{content:\"<<!escape_string:header.separators.group_after>>\";}\n" +
						"$.header_board_group+$.header_board_group{margin-left:<<header.spacing.group>>px;}\n" +
						"$.header_board{display:inline-block;}\n" +
						"$.header_board+$.header_board:before{content:\"<<!escape_string:header.separators.group_inner>>\"}\n" +
						"$.header_board_link{padding:0px <<header.spacing.board>>px 0px <<header.spacing.board>>px;text-decoration:none;color:<<header.boards.colors.link>>;}\n" +
						"$.header_board_link:hover{color:<<header.boards.colors.link_hover>>;}\n" +
						"$.header_board:not(.named)>$.header_board_link>$.header_board_text_name," +
						"$.header_board:not(.current)>$.header_board_link>$.header_board_text_name{display:none;}\n" +
						"$.header_board.current.named>$.header_board_link>$.header_board_text_short{display:none;}\n" +
						"$.header_board.current>$.header_board_link{color:<<header.boards.current.colors.link>>;text-shadow:<<!shadow:header.boards.current.shadow>>;<<!font:header.boards.current.font>>}\n" +
						"$.header_board.current>$.header_board_link:hover{color:<<header.boards.current.colors.link_hover>>;text-shadow:<<!shadow:header.boards.current.shadow.hover>>;}\n" +

						"$.header_custom{display:inline-block;float:right;position:relative;white-space:nowrap;}\n" +
						"$.header_options{display:block;padding-bottom:<<header.padding>>px;}\n" +
						"$.header_option{display:inline-block;cursor:pointer;text-decoration:none;color:<<header.options.colors.link>>;}\n" +
						"$.header_option:not(.no_link):hover{color:<<header.options.colors.link_hover>>;}\n" +
						"$.header_option+$.header_option{margin-left:<<header.spacing.options>>px;}\n" +
						//}

						//{ Menu
						"$.menu{position:absolute;display:inline-block;z-index:150;<<!font:menu.font>>}\n" +
						"$.menu.small{position:absolute;display:inline-block;z-index:150;<<!font:menu.font_small>>}\n" +
						"$.menu.fixed{position:fixed;}\n" +
						"$.menu.hidden{display:none;}\n" +
						"$.menu_background{position:absolute;left:0;top:0;bottom:0;right:0;box-shadow:<<!shadow:menu.shadow>>;opacity:<<menu.shadow.opacity>>;}\n" +
						"$.menu_options{position:relative;}\n" +
						"$.menu_option{white-space:nowrap;position:relative;padding:<<menu.padding.top>>px <<menu.padding.right>>px <<menu.padding.bottom>>px <<menu.padding.left>>px;cursor:pointer;<<css.box-sizing>>:border-box;text-decoration:none;position:relative;display:block;color:<<menu.colors.text>>;}\n" +
						"$.menu_option:hover{position:relative;display:block;color:<<menu.colors.text_hover>>;}\n" +
						"$.menu_option_background{position:absolute;left:0;top:0;bottom:0;right:0;background-color:<<menu.colors.bg>>;border-width:<<menu.border.size>>px <<menu.border.size>>px 0px <<menu.border.size>>px;border-style:<<menu.border.style>>;border-color:<<menu.border.color>> <<menu.border.color>> <<menu.border.color>> <<menu.border.color>>;}\n" +
						"$.menu_option:hover>$.menu_option_background{background-color:<<menu.colors.bg_hover>>;border-color:<<menu.border.color_hover>> <<menu.border.color_hover>> <<menu.border.color_hover>> <<menu.border.color_hover>>;}\n" +
						"$.menu_option:hover+$.menu_option>$.menu_option_background{border-top-color:<<menu.border.color_hover>>;}\n" +
						"$.menu_option:last-child>$.menu_option_background{border-bottom-width:1px;}\n" +
						"$.menu_option>$.menu_option_content{position:relative;}\n" +

						"$.menu:not(.expandable_on_left)>$.menu_options.expandable>$.menu_option>$.menu_option_content:after{content:\"\";display:inline-block;width:1.5em;text-align:right;}\n" +
						"$.menu:not(.expandable_on_left):not(.expandable_point_left)>$.menu_options.expandable>$.menu_option.expandable>$.menu_option_content:after{content:\"<<!escape_string:symbols.triangles.right>>\";}\n" +
						"$.menu:not(.expandable_on_left).expandable_point_left>$.menu_options.expandable>$.menu_option.expandable>$.menu_option_content:after{content:\"<<!escape_string:symbols.triangles.left>>\";}\n" +
						"$.menu.expandable_on_left>$.menu_options.expandable>$.menu_option>$.menu_option_content:before{content:\"\";display:inline-block;width:1.5em;text-align:left;}\n" +
						"$.menu.expandable_on_left:not(.expandable_point_left)>$.menu_options.expandable>$.menu_option.expandable>$.menu_option_content:before{content:\"<<!escape_string:symbols.triangles.right>>\";}\n" +
						"$.menu.expandable_on_left.expandable_point_left>$.menu_options.expandable>$.menu_option.expandable>$.menu_option_content:before{content:\"<<!escape_string:symbols.triangles.left>>\";}\n" +
						//}

						//{ Page selector
						"$.menu_option.page_selection{text-align:center;}\n" +
						//}

						//{ Popups
						"$.popup{position:fixed;z-index:150;<<css.box-sizing>>:border-box;color:<<popup.colors.text>>;padding:<<popup.bg.size>>px;<<!font:popup.font>>}\n" +
						"$.popup.active{z-index:151;}\n" +
						"$.popup.hidden{display:none;}\n" +
						"$.popup_content{position:relative;display:table;table-layout:fixed;height:100%;width:100%;<<css.box-sizing>>:border-box;}\n" +
						"$.popup_title_bar{cursor:move;height:0;}\n" +

						"$.popup_title{text-shadow:<<!shadow:popup.title.shadow>>;<<!font:popup.title.font>>}\n" +

						"$.popup_body{height:100%;}\n" +
						"$.popup_body_inner_1{height:100%;<<css.box-sizing>>:border-box;}\n" +
						"$.popup_body_inner_2{position:relative;height:100%;}\n" +
						"$.popup_body_inner_3{position:relative;overflow:auto;height:100%;}\n" +
						"$.popup_body_inner_3.no_scroll{overflow:visible;}\n" +
						"$.popup_content_item{display:table-row;}\n" +
						"$.popup_content_item_inner{<<css.box-sizing>>:border-box;}\n" +

						"$.popup_close{cursor:pointer;position:absolute;right:0;top:0;color:<<popup.close.color>>;padding:0px <<popup.close.padding>>px 0px <<popup.close.padding>>px;}\n" +
						"$.popup_close:hover{color:<<popup.close.color_hover>>;}\n" +
						"$.popup_close:after{content:\"<<symbols.mult>>\";}\n" +
						"$.popup_close.hidden{display:none;}\n" +

						"$.popup_description{height:100%;<<!font:popup.description.font>>}\n" +
						"$.popup_input{display:block;width:100%;<<css.box-sizing>>:border-box;resize:none;<<!font:popup.input.font>>}\n" +
						"$.popup_input," +
						"$.popup_button{margin:0px;padding:<<popup.input.padding>>px;background-color:<<popup.input.colors.bg>>;color:<<popup.input.colors.text>>;border:<<popup.input.border.size>>px <<popup.input.border.style>> <<popup.input.border.color>>;}\n" +
						"$.popup_input:hover," +
						"$.popup_button:hover{background-color:<<popup.input.colors.bg_hover>>;border-color:<<popup.input.border.color_hover>>;}\n" +
						"$.popup_input:focus," +
						"$.popup_button:active{background-color:<<popup.input.colors.bg_selected>>;border-color:<<popup.input.border.color_selected>>;}\n" +

						"$.popup_content:not(.packed)>$.popup_content_item+$.popup_content_item>$.popup_content_item_inner{padding-top:<<popup.separation>>px;}\n" +
						"$.popup_description+$.popup_input{margin-top:<<popup.separation>>px;}\n" +

						"$.popup_buttons{text-align:right;height:0;white-space:nowrap;}\n" +

						"$.popup_input{color:<<popup.input.colors.text>>;<<!font:popup.input.font>>}\n" +
						"$.popup_input::-webkit-input-placeholder{color:<<popup.input.colors.placeholder>>;opacity:<<popup.input.colors.placeholder_opacity>>;}\n" +
						"$.popup_input:-moz-placeholder{color:<<popup.input.colors.placeholder>>;opacity:<<popup.input.colors.placeholder_opacity>>;}\n" +
						"$.popup_input::-moz-placeholder{color:<<popup.input.colors.placeholder>>;opacity:<<popup.input.colors.placeholder_opacity>>;}\n" +
						"$.popup_input:-ms-input-placeholder{color:<<popup.input.colors.placeholder>>;opacity:<<popup.input.colors.placeholder_opacity>>;}\n" +

						"$.popup_button{white-space:normal;cursor:pointer;display:inline-block;<<css.box-sizing>>:border-box;<<!font:popup.input.font>>}\n" +
						"$.popup_button.text_only{background-color:transparent;border-color:transparent;}\n" +
						"$.popup_button+$.popup_button{margin-left:<<popup.buttons.spacing>>px;}\n" +

						"$.popup.active>$.popup_background{opacity:<<popup.bg.active_opacity>>;}\n" +
						"$.popup_background{position:absolute;left:0;top:0;bottom:0;right:0;background-color:<<popup.bg.color>>;<<css.border-radius>>:<<popup.bg.radius>>px;box-shadow:<<!shadow:popup.bg.shadow>>;opacity:<<popup.bg.opacity>>;}\n" +
						"$.popup_resizers{position:absolute;left:0;top:0;bottom:0;right:0;}\n" +
						"$.popup_resizers.hidden{display:none;}\n" +
						"$.popup_resizer${position:absolute;}\n" +
						"$.popup_resizer.top{height:<<popup.resizers.size>>px;top:0;cursor:n-resize;}" +
						"$.popup_resizer.bottom{height:<<popup.resizers.size>>px;bottom:0;cursor:n-resize;}\n" +
						"$.popup_resizer.left{width:<<popup.resizers.size>>px;left:0;cursor:e-resize;}" +
						"$.popup_resizer.right{width:<<popup.resizers.size>>px;right:0;cursor:e-resize;}\n" +
						"$.popup_resizer.top:not(.left):not(.right)," +
						"$.popup_resizer.bottom:not(.left):not(.right){left:<<popup.resizers.size>>px;right:<<popup.resizers.size>>px;}\n" +
						"$.popup_resizer.left:not(.top):not(.bottom)," +
						"$.popup_resizer.right:not(.top):not(.bottom){top:<<popup.resizers.size>>px;bottom:<<popup.resizers.size>>px;}\n" +
						"$.popup_resizer.top.left{cursor:se-resize;}\n" +
						"$.popup_resizer.top.right{cursor:sw-resize;}\n" +
						"$.popup_resizer.bottom.right{cursor:se-resize;}\n" +
						"$.popup_resizer.bottom.left{cursor:sw-resize;}\n" +
						//}

						//{ Quick reply
						"$.quick_reply{position:fixed;display:inline-block;}\n" +
						"$.quick_reply.closed{display:none;}\n" +

						"$.qr_content{position:relative;z-index:5;}\n" +
						"$.qr_row{display:block;line-height:0px;white-space:nowrap;position:relative;}\n" +
						"$.qr_cell{vertical-align:middle;display:inline-block;<<css.box-sizing>>:border-box;position:relative;line-height:normal;white-space:normal;}\n" +

						"$.qr_cell_inner{border:<<popup.input.border.size>>px solid transparent;<<css.box-sizing>>:border-box;width:100%;height:100%;padding:0px;}\n" +
						"$.qr_cell_inner_b," +
						"$.qr_cell_inner_l," +
						"$.qr_cell_inner_t{position:absolute;z-index:-3;<<css.box-sizing>>:border-box;border:<<popup.input.border.size>>px <<popup.input.border.style>> <<popup.input.border.color>>;}" +
						"$.qr_cell_inner_b{background-color:<<popup.input.colors.bg>>;left:0;top:0;bottom:0;right:0;}\n" +
						"$.qr_cell_inner_l{right:100%;top:0;bottom:0;border-left-width:0px;}\n" +
						"$.qr_cell_inner_t{left:0;bottom:100%;right:0;border-top-width:0px;}\n" +

						"$.qr_cell:nth-of-type(1)>$.qr_cell_inner>$.qr_cell_inner_l{display:none;}\n" +
						"$.qr_cell:nth-of-type(n+2)>$.qr_cell_inner,\n" +
						"$.qr_cell:nth-of-type(n+2)>$.qr_cell_inner>$.qr_cell_inner_b{border-left-width:0px;}\n" +
						"$.qr_row.top>$.qr_cell>$.qr_cell_inner>$.qr_cell_inner_t," +
						"$.qr_content:not(.packed)>$.qr_row>$.qr_cell>$.qr_cell_inner>$.qr_cell_inner_t{display:none;}\n" +
						"$.qr_content.packed>$.qr_row:not(.top)>$.qr_cell>$.qr_cell_inner," +
						"$.qr_content.packed>$.qr_row:not(.top)>$.qr_cell>$.qr_cell_inner>$.qr_cell_inner_b{border-top-width:0px;}\n" +

						"$.qr_row.top>$.qr_cell:first-child>$.qr_cell_inner>$.qr_cell_inner_b{<<css.border-top-left-radius>>:<<popup.input.border.radius>>px;}\n" +
						"$.qr_row.top>$.qr_cell:last-child>$.qr_cell_inner>$.qr_cell_inner_b{<<css.border-top-right-radius>>:<<popup.input.border.radius>>px;}\n" +
						"$.qr_row.bottom>$.qr_cell:first-child>$.qr_cell_inner>$.qr_cell_inner_b{<<css.border-bottom-left-radius>>:<<popup.input.border.radius>>px;}\n" +
						"$.qr_row.bottom>$.qr_cell:last-child>$.qr_cell_inner>$.qr_cell_inner_b{<<css.border-bottom-right-radius>>:<<popup.input.border.radius>>px;}\n" +

						"$.qr_input:hover+$.qr_cell_inner_b," +
						"$.qr_input:hover+$.qr_cell_inner_b+$.qr_cell_inner_l," +
						"$.qr_input:hover+$.qr_cell_inner_b+$.qr_cell_inner_l+$.qr_cell_inner_t{z-index:-2;border-color:<<popup.input.border.color_hover>>;}" +
						"$.qr_input:hover+$.qr_cell_inner_b{background-color:<<popup.input.colors.bg_hover>>;}\n" +

						"$.qr_input:focus+$.qr_cell_inner_b," +
						"$.qr_input:focus+$.qr_cell_inner_b+$.qr_cell_inner_l," +
						"$.qr_input:focus+$.qr_cell_inner_b+$.qr_cell_inner_l+$.qr_cell_inner_t," +
						"$.qr_button:active+$.qr_cell_inner_b," +
						"$.qr_button:active+$.qr_cell_inner_b+$.qr_cell_inner_l," +
						"$.qr_button:active+$.qr_cell_inner_b+$.qr_cell_inner_l+$.qr_cell_inner_t{z-index:-1;border-color:<<popup.input.border.color_selected>>;}" +
						"$.qr_input:focus+$.qr_cell_inner_b," +
						"$.qr_button:active+$.qr_cell_inner_b{background-color:<<popup.input.colors.bg_selected>>;}\n" +

						"$.qr_cell.no_bg>$.qr_cell_inner{border-width:0px;}\n" +
						"$.qr_cell.no_bg>$.qr_cell_inner>$.qr_cell_inner_l," +
						"$.qr_cell.no_bg>$.qr_cell_inner>$.qr_cell_inner_t{display:none;}" +
						"$.qr_cell.no_bg>$.qr_cell_inner>$.qr_cell_inner_b{border-width:0px;background-color:transparent;}\n" +

						"$.popup_input.qr_input," +
						"$.popup_input.qr_input:hover," +
						"$.popup_input.qr_input:focus{width:100%;margin:0px;padding:<<popup.input.padding>>px;resize:none;border:0px hidden;background-color:transparent;<<css.box-sizing>>:border-box;}\n" +

						"$.qr_content:not(.packed)>$.qr_row+$.qr_row{margin-top:<<popup.separation>>px;}\n" +
						//}

						//{ Quick reply items
						"$.qr_row.grab{cursor:move;}\n" +

						"$.qr_submit{cursor:pointer;width:100%;display:block;text-align:center;}\n" +
						"$.popup_button.qr_submit{background-color:transparent;border:0px hidden;<<css.box-sizing>>:border-box;padding:0px;margin:0px;}\n" +
						"$.qr_submit:active{}\n" +
						"$.qr_submit_text:before{content:\"<<!escape_string:qr.submit.text>>\";}\n" +
						"$.qr_submit.active>$.qr_submit_text:before{content:\"<<!escape_string:qr.submit.text_active>>\";}\n" +
						"$.qr_submit.retry>$.qr_submit_text:before{content:\"<<!escape_string:qr.submit.text_retry>>\";}\n" +

						"$.qr_main_label{white-space:nowrap;display:inline-block;<<!font:qr.label.font>>}\n" +
						"$.qr_main_label_prefix{cursor:default;color:<<qr.label.color>>;cursor:pointer;}\n" +
						"$.qr_main_label_prefix:hover{color:<<qr.label.color_hover>>;}\n" +
						"$.qr_main_label_prefix:before{content:\"<<!escape_string:qr.label.text.quick_reply>>\";}\n" +
						"$.qr_main_label_target{cursor:default;}\n" +
						"$.qr_main_label_target:before{content:\"<<!escape_string:qr.target.text.left>>\";padding-right:<<qr.target.padding.inner>>px;color:<<!escape_string:qr.target.text.color>>;}\n" +
						"$.qr_main_label_target:after{content:\"<<!escape_string:qr.target.text.right>>\";padding-left:<<qr.target.padding.inner>>px;color:<<!escape_string:qr.target.text.color>>;}\n" +
						"$.qr_main_label_target_text{color:<<qr.target.color>>;cursor:pointer;text-decoration:none;}\n" +
						"$.qr_main_label_target_text:hover{color:<<qr.target.color_hover>>;}\n" +
						"$.qr_main_label_target_text:after{content:attr(target_thread);}\n" +

						"$.qr_main_label.persistent>$.qr_main_label_prefix:before{content:\"<<!escape_string:qr.label.text.reply>>\";}\n" +
						"$.qr_main_label[target_thread=\"new\"]>$.qr_main_label_prefix{display:none;}\n" +
						"$.qr_main_label[target_thread=\"current\"]>$.qr_main_label_target{display:none;}\n" +
						"$.qr_main_label:not([target_thread=\"new\"])>$.qr_main_label_prefix+$.qr_main_label_target{padding-left:<<qr.target.padding.outer>>px;}\n" +

						"$.qr_close{cursor:pointer;color:<<popup.close.color>>;padding:0px <<popup.close.padding>>px 0px <<popup.close.padding>>px;}\n" +
						"$.qr_close:hover{color:<<popup.close.color_hover>>;}\n" +
						"$.qr_close:after{content:\"<<!escape_string:symbols.mult>>\";}\n" +

						"$.qr_file_settings{position:absolute;right:0;top:0;bottom:0;white-space:nowrap;line-height:0;padding:0px;margin:0px;z-index:1;}\n" +
						"$.qr_file_settings.hidden{display:none;}\n" +
						"$.qr_file_settings_inner," +
						"$.qr_file_settings_v_aligner{vertical-align:middle;white-space:normal;line-height:normal;}\n" +
						"$.qr_file_settings_v_aligner{width:0px;height:100%;display:inline-block;}\n" +
						"$.qr_file_settings_inner{line-height:0;display:inline-block;<<css.box-sizing>>:border-box;border-right:<<popup.input.border.size>>px solid transparent;padding-right:<<popup.input.padding>>px;}\n" +
						"$.qr_file_settings_item{vertical-align:middle;display:inline-block;line-height:normal;}\n" +
						"$.qr_file_spoiler.hidden{display:none;}\n" +
						"$.qr_file_remove{color:<<popup.input.colors.link>>;text-decoration:none;padding-left:<<qr.target.padding.inner>>px;cursor:pointer;}\n" +
						"$.qr_file_remove:hover{color:<<popup.input.colors.link_hover>>;}\n" +
						"$.qr_file_remove:after{content:\"<<!escape_string:symbols.mult>>\";}\n" +
						"$.qr_file{display:none;}\n" +

						"$.qr_saved_settings{position:absolute;right:0;top:0;bottom:0;white-space:nowrap;line-height:0;padding:0px;margin:0px;z-index:1;}\n" +
						"$.qr_saved_settings.hidden{display:none;}\n" +
						"$.qr_saved_settings_inner," +
						"$.qr_saved_settings_v_aligner{vertical-align:middle;white-space:normal;line-height:normal;}\n" +
						"$.qr_saved_settings_v_aligner{width:0px;height:100%;display:inline-block;}\n" +
						"$.qr_saved_settings_inner{line-height:0;display:inline-block;<<css.box-sizing>>:border-box;border-right:<<popup.input.border.size>>px solid transparent;padding-right:<<popup.input.padding>>px;}\n" +
						"$.qr_saved_settings_unsave{vertical-align:middle;display:inline-block;line-height:normal;}\n" +
						"$.qr_saved_settings_unsave{color:<<popup.input.colors.placeholder>>;text-decoration:none;padding-left:<<qr.target.padding.inner>>px;cursor:pointer;}\n" +
						"$.qr_saved_settings_unsave:hover{color:<<popup.input.colors.link_hover>>;}\n" +
						"$.qr_saved_settings_unsave:after{content:\"<<!escape_string:symbols.mult>>\";}\n" +
						"$.qr_row.top>$.qr_cell>$.qr_cell_inner>$.qr_saved_settings>$.qr_saved_settings_inner{border-top:<<popup.input.border.size>>px solid transparent;}\n" +

						"$.qr_status{display:block;}\n" +
						"$.qr_status.hidden{display:none;}\n" +
						"$.qr_status_inner{padding:<<qr.status.padding.top>>px 0px <<qr.status.padding.bottom>>px 0px;}\n" +
						"$.qr_status_text{white-space:nowrap;color:<<qr.status.text_color>>;<<!font:qr.status.font>>}\n" +
						"$.qr_status_attempt{padding-left:<<qr.status.spacing>>px;}\n" +
						"$.qr_status_attempt.hidden{display:none;}\n" +
						"$.qr_progress{height:<<qr.status.bar_height>>px;width:100%;display:block;position:relative;}\n" +
						"$.qr_progress.default{background-color:<<qr.status.default.color>>;}\n" +
						"$.qr_progress.parse{background-color:<<qr.status.parse.color>>;}\n" +
						"$.qr_progress.upload{background-color:<<qr.status.upload.color>>;}\n" +
						"$.qr_progress.cooldown{background-color:<<qr.status.cooldown.color>>;}\n" +
						"$.qr_progress.download{background-color:<<qr.status.download.color>>;}\n" +
						"$.qr_progress.context_acquire{background-color:<<qr.status.context_acquire.color>>;}\n" +
						"$.qr_progress_inner{position:absolute;left:0;width:0px;bottom:0;top:0;}\n" +
						"$.qr_progress_inner.parse{background-color:<<qr.status.parse.color>>;}\n" +
						"$.qr_progress_inner.upload{background-color:<<qr.status.upload.color>>;}\n" +
						"$.qr_progress_inner.cooldown{background-color:<<qr.status.cooldown.color>>;}\n" +
						"$.qr_progress_inner.download{background-color:<<qr.status.download.color>>;}\n" +
						"$.qr_progress_inner.context_acquire{background-color:<<qr.status.context_acquire.color>>;}\n" +

						"$.qr_cooldown_container{overflow:hidden;}\n" +
						"$.qr_cooldown{position:absolute;left:0;top:0;bottom:0;right:0;opacity:<<qr.cooldown.opacity>>;<<css.transition>>:opacity 0s;background-color:<<qr.cooldown.colors.bg>>;}\n" +
						"$.qr_cooldown.ready{opacity:0;<<css.transition>>:opacity <<qr.cooldown.transition.time>>s <<qr.cooldown.transition.method>> <<qr.cooldown.transition.delay>>s;}\n" +
						"$.qr_cooldown_inner{position:absolute;left:0;top:0;bottom:0;width:0%;background-color:<<qr.cooldown.colors.bar>>;}\n" +
						//}

						//{ Settings
						"$.settings{position:absolute;left:0;top:0;bottom:0;right:0;<<!font:settings.font>>}\n" +
						"$.settings_container{display:table;table-layout:fixed;width:100%;height:100%;}\n" +

						"$.settings_tabs{display:table-row;height:0;width:100%;white-space:nowrap;}\n" +
						"$.settings_tabs_inner_1{display:block;overflow-x:auto;overflow-y:hidden;}\n" +
						"$.settings_tabs_inner_2{display:table;width:100%;}\n" +
						"$.settings_tabs_end_spacing{position:relative;vertical-align:middle;line-height:normal;width:0;display:table-cell;<<css.box-sizing>>:border-box;border-bottom:<<settings.tabs.border_outer.size>>px <<settings.tabs.border_outer.style>> <<settings.tabs.border_outer.color>>;}\n" +
						"$.settings_tabs_end_spacing:last-child{width:100%;}\n" +
						"$.settings_tabs_end_spacing:not(:last-child):before{content:\"\";width:<<settings.tabs.spacing.outer>>px;display:inline-block;}\n" +
						"$.settings_tabs_inner_3{display:table-cell;width:0;<<css.box-sizing>>:border-box;}\n" +

						"$.settings_tabs_end_spacing_inner{display:block;text-align:right;line-height:normal;<<css.box-sizing>>:border-box;padding-right:<<settings.tabs.spacing.outer>>px;}\n" +
						"$.settings_tabs_end_spacing_mode{white-space:nowrap;padding-left:<<settings.tabs.spacing.outer>>px;cursor:pointer;color:<<settings.tabs.mode_select.color>>;}\n" +
						"$.settings_tabs_end_spacing_mode:hover{color:<<settings.tabs.mode_select.color_hover>>;}\n" +
						"$.settings_tabs_end_spacing_mode:after{content:attr(value_display);}\n" +

						"$.settings_tab{display:inline-block;<<css.box-sizing>>:border-box;}\n" +
						"$.settings_tab_inner_1{cursor:pointer;position:relative;vertical-align:bottom;display:inline-block;white-space:normal;}\n" +
						"$.settings_tab_inner_2{border-width:<<settings.tabs.border_outer.size>>px <<settings.tabs.border_outer.size>>px 0px <<settings.tabs.border_outer.size>>px;border-color:<<settings.tabs.border_outer.color>>;border-style:<<settings.tabs.border_outer.style>>;padding:4px 8px 4px 8px;<<css.border-top-left-radius>>:<<settings.tabs.border_outer.radius>>px;<<css.border-top-right-radius>>:<<settings.tabs.border_outer.radius>>px;overflow:hidden;text-shadow:<<!shadow:settings.tabs.title.shadow>>;<<!font:settings.tabs.title.font>>}\n" +
						"$.settings_tab:not(.current)>$.settings_tab_inner_1>$.settings_tab_inner_2{border-color:<<settings.tabs.border_outer.color_inactive>>;}\n" +
						"$.settings_tab.current>$.settings_tab_inner_1>$.settings_tab_inner_2{text-shadow:<<!shadow:settings.tabs.title.current.shadow>>;<<!gradient:settings.tabs.bg>>}\n" +
						"$.settings_tab:not(.first):before{content:\"\";width:<<settings.tabs.spacing.inner>>px;display:inline-block;vertical-align:bottom;border-bottom:<<settings.tabs.border_outer.size>>px <<settings.tabs.border_outer.style>> <<settings.tabs.border_outer.color>>;}\n" +
						"$.settings_tab:not(.current)>$.settings_tab_inner_1>$.settings_tab_inner_2{padding-bottom:2px;}\n" +
						"$.settings_tab:not(.current)>$.settings_tab_inner_1:after{content:\"\";position:absolute;left:0;right:0;bottom:0;border-bottom:<<settings.tabs.border_outer.size>>px <<settings.tabs.border_outer.style>> <<settings.tabs.border_outer.color>>;}\n" +

						"$.settings_sections{display:table-row;height:100%;}\n" +
						"$.settings_sections_inner_1{position:relative;height:100%;<<css.box-sizing>>:border-box;padding-top:4px;}\n" +
						"$.settings_sections_inner_2{position:relative;height:100%;}\n" +
						"$.settings_sections_inner_3{position:absolute;left:0;top:0;bottom:0;right:0;overflow:auto;}\n" +

						"$.settings_content{display:block;}\n" +
						"$.settings_content.hidden{display:none;}\n" +

						"$.settings_section{display:block;}\n" +
						"$.settings_section.hidden{display:none;}\n" +

						"$.settings_section:not(.first):before{content:\"\";margin:<<settings.section.separator.padding.top>>px <<settings.section.separator.padding.right>>px <<settings.section.separator.padding.bottom>>px <<settings.section.separator.padding.left>>px;border-width:<<settings.section.separator.size>>px 0px <<settings.section.separator.size>>px 0px;border-color:<<settings.section.separator.colors.top>>;border-bottom-color:<<settings.section.separator.colors.bottom>>;border-style:<<settings.section.separator.style>>;display:block;}\n" +
						"$.settings_section_title{<<!font:settings.section.label.font>>}\n" +
						"$.settings_section_content{margin:<<settings.section.padding.top>>px <<settings.section.padding.right>>px <<settings.section.padding.bottom>>px <<settings.section.padding.left>>px;}\n" +

						"$.settings_section_item{position:relative;padding:<<settings.section.item.padding>>px;}\n" +
						"$.settings_section_item.odd{background-color:<<settings.section.item.colors.bg_odd>>;}\n" +
						"$.settings_section_item.hidden{display:none;}\n" +
						"$.settings_section_item:hover{z-index:1;background-color:<<settings.section.item.colors.bg_hover>>;box-shadow:<<!shadow:settings.section.item.hover_shadow>>;}\n" +
						"$.settings_section_item:after{content:\"\";display:block;clear:both;}\n" +

						"$.settings_section_item_label{position:relative;<<!font:settings.section.item.label.font>>}\n" +
						"$.settings_section_item.changed>$.settings_section_item_label:before{position:absolute;right:100%;top:0;content:\"<<settings.section.change_indicator.text>>\";margin:0px <<settings.section.item.padding>>px;color:<<settings.section.change_indicator.color>>;}\n" +

						"$.settings_value_container{position:relative;z-index:1;color:<<settings.inputs.text_color.normal>>;}\n" +
						"$.settings_section_item.active>$.settings_value_container," +
						"$.settings_section_item:hover>$.settings_value_container{color:<<settings.inputs.text_color.hover>>;}\n" +
						"$.settings_value_container:not(.full){float:right;}\n" +
						"$.settings_value_container.full{margin-top:<<settings.section.item.padding>>px;}\n" +
						"$.settings_value_item{display:block;text-align:right;}\n" +

						"$.settings_value_input{width:<<settings.inputs.textbox.width>>px;}\n" +
						"$.settings_value_textarea_input{width:100%;height:100px;}\n" +

						"$.settings_value_text_display{cursor:pointer;}\n" +
						"$.settings_value_text_display:after{content:attr(display_value);}\n" +

						"$.settings_value_checkbox_container{direction:rtl;display:block;}\n" +
						"$.settings_value_checkbox_display{vertical-align:middle;line-height:normal;;}\n" +
						"$.settings_value_checkbox_container>$.checkbox_input+$.checkbox_indicator+$.settings_value_checkbox_display:after{content:attr(value_off);margin-right:<<settings.inputs.checkbox.separation.inner>>px;direction:ltr;}\n" +
						"$.settings_value_checkbox_container>$.checkbox_input:checked+$.checkbox_indicator+$.settings_value_checkbox_display:after{content:attr(value_on);}\n" +

						"$.settings_value_radio_select_container{}\n" +
						"$.settings_value_radiobox_container{direction:rtl;display:block;}\n" +
						"$.settings_value_radio_select_item{display:block;}\n" +
						"$.settings_value_radio_select_container.single_line>$.settings_value_radio_select_item{display:inline-block;}\n" +
						"$.settings_value_radio_select_container.single_line>$.settings_value_radio_select_item+$.settings_value_radio_select_item{margin-left:<<settings.inputs.checkbox.separation.outer>>px;}\n" +
						"$.settings_value_radiobox_display{vertical-align:middle;line-height:normal;}\n" +
						"$.settings_value_radiobox_container>$.radiobox_input+$.radiobox_indicator+$.settings_value_radiobox_display:after{content:attr(value_display);margin-right:<<settings.inputs.checkbox.separation.inner>>px;direction:ltr;}\n" +
						"$.settings_section_item:hover>$.settings_value_container>$.settings_value_item>$.settings_value_radio_select_container>$.settings_value_radio_select_item>$.settings_value_radiobox_container>$.radiobox_input:not(:checked)+$.radiobox_indicator+$.settings_value_radiobox_display:after{color:<<settings.inputs.checkbox.colors.non_current>>;}\n" +

						"$.settings_value_slider{text-align:right;}\n" +
						"$.settings_value_slider_inner_1{position:relative;width:<<settings.inputs.slider.width>>px;height:<<settings.inputs.slider.height>>px;overflow:hidden;}\n" +
						"$.settings_value_slider_inner_1:before{content:\"\";vertical-align:middle;display:inline-block;width:0;height:100%;}\n" +
						"$.settings_value_slider_inner_1:after{content:\"\";vertical-align:middle;display:inline-block;width:100%;border-width:<<settings.inputs.slider.line_size>>px 0px <<settings.inputs.slider.line_size>>px 0px;border-style:solid;border-color:<<settings.inputs.slider.colors.line.top>>;border-bottom-color:<<settings.inputs.slider.colors.line.bottom>>;}\n" +
						"$.settings_value_slider_inner_2{position:absolute;top:0;bottom:0;left:<<settings.inputs.slider.grabber_size>>px;right:<<settings.inputs.slider.grabber_size>>px;}\n" +
						"$.settings_value_slider_grabber{position:absolute;left:0;top:50%;height:100%;cursor:pointer;}\n" +
						"$.settings_value_slider_grabber:after{content:\"\";position:absolute;left:-<<settings.inputs.slider.grabber_size>>px;width:<<!double:settings.inputs.slider.grabber_size>>px;top:-50%;height:100%;background-color:<<settings.inputs.slider.colors.grabber.bg>>;<<css.box-sizing>>:border-box;border:<<settings.inputs.slider.border.size>>px <<settings.inputs.slider.border.style>> <<settings.inputs.slider.border.color>>;<<css.border-radius>>:<<settings.inputs.slider.border.radius>>px;}\n" +
						"$.settings_value_slider_value_display{}\n" +
						"$.settings_value_slider_value_display[display_value]:after{content:attr(display_value);}\n" +
						"$.settings_value_slider_value_display[display_value][display_units]:after{content:attr(display_value) \" \" attr(display_units);}\n" +
						//}

						//{ Messenger
						"$.message_container{position:fixed;left:50%;top:0;width:<<messages.size.width>>px;}\n" +
						"$.message{position:absolute;z-index:160;left:-50%;width:100%;padding:<<messages.padding.top>>px <<messages.padding.right>>px <<messages.padding.bottom>>px <<messages.padding.left>>px;<<css.box-sizing>>:border-box;}\n" +

						"$.message>$.message_background{position:absolute;left:0;top:0;bottom:0;right:0;opacity:<<messages.bg.opacity>>;box-shadow:<<!shadow:messages.bg.shadow>>;<<css.border-radius>>:<<messages.border.radius>>px;}\n" +
						"$.message.plain>$.message_background{background-color:<<messages.colors.plain.bg>>;}\n" +
						"$.message.error>$.message_background{background-color:<<messages.colors.error.bg>>;}\n" +
						"$.message.parse>$.message_background{background-color:<<messages.colors.parse.bg>>;}\n" +
						"$.message.okay>$.message_background{background-color:<<messages.colors.okay.bg>>;}\n" +
						"$.message.good>$.message_background{background-color:<<messages.colors.good.bg>>;}\n" +
						"$.message.info>$.message_background{background-color:<<messages.colors.info.bg>>;}\n" +

						"$.message.minimized>$.message_background{opacity:<<messages.bg.opacity>>;box-shadow:<<!shadow:messages.bg.shadow_min>>;}\n" +
						"$.message.minimized{left:50%;margin-left:8px;width:auto;}\n" +
						"$.message.minimized>$.message_inner>$:not(.message_buttons){display:none;}\n" +
						"$.message.minimized>$.message_inner>$.message_buttons{position:relative;}\n" +

						"$.message.plain{color:<<messages.colors.plain.text>>;}\n" +
						"$.message.error{color:<<messages.colors.error.text>>;}\n" +
						"$.message.parse{color:<<messages.colors.parse.text>>;}\n" +
						"$.message.okay{color:<<messages.colors.okay.text>>;}\n" +
						"$.message.good{color:<<messages.colors.good.text>>;}\n" +
						"$.message.info{color:<<messages.colors.info.text>>;}\n" +
						"$.message_inner{position:relative;width:100%;}\n" +
						"$.message_title{padding-bottom:<<messages.title.separation>>px;<<!font:messages.title.font>>}\n" +

						"$.message_buttons{position:absolute;right:0;top:0;text-align:right;line-height:100%;}\n" +
						"$.message_button{display:block;text-decoration:none;cursor:pointer;z-index:1;position:relative;}\n" +
						"$.message_button.close:after{content:\"<<!escape_string:symbols.mult>>\";}\n" +
						"$.message_button.minimize:after{content:\"<<!escape_string:symbols.minus>>\";}\n" +
						"$.message_timer{display:block;opacity:<<messages.timer.opacity>>;position:relative;top:-0.5em;cursor:default;<<!font:messages.timer.font>>}\n" +

						"$.message_body{<<!font:messages.body.font>>}\n" +

						"$.message.plain>$.message_inner $.message_shadow," +
						"$.message.plain>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.plain.title_shadow>>;}\n" +
						"$.message.error>$.message_inner $.message_shadow," +
						"$.message.error>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.error.title_shadow>>;}\n" +
						"$.message.parse>$.message_inner $.message_shadow," +
						"$.message.parse>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.parse.title_shadow>>;}\n" +
						"$.message.okay>$.message_inner $.message_shadow," +
						"$.message.okay>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.okay.title_shadow>>;}\n" +
						"$.message.good>$.message_inner $.message_shadow," +
						"$.message.good>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.good.title_shadow>>;}\n" +
						"$.message.info>$.message_inner $.message_shadow," +
						"$.message.info>$.message_inner $.message_hover_shadow:hover{text-shadow:<<messages.title.shadow.offset.left>>px <<messages.title.shadow.offset.top>>px <<messages.title.shadow.blur>>px <<messages.colors.info.title_shadow>>;}\n" +

						"$.message_link{cursor:pointer;<<!font:messages.link.font>>}\n" +
						"$.message.plain $.message_link{color:<<messages.colors.plain.link>>;}\n" +
						"$.message.plain $.message_link:hover{color:<<messages.colors.plain.link_hover>>;}\n" +
						"$.message.error $.message_link{color:<<messages.colors.error.link>>;}\n" +
						"$.message.error $.message_link:hover{color:<<messages.colors.error.link_hover>>;}\n" +
						"$.message.parse $.message_link{color:<<messages.colors.parse.link>>;}\n" +
						"$.message.parse $.message_link:hover{color:<<messages.colors.parse.link_hover>>;}\n" +
						"$.message.okay $.message_link{color:<<messages.colors.okay.link>>;}\n" +
						"$.message.okay $.message_link:hover{color:<<messages.colors.okay.link_hover>>;}\n" +
						"$.message.good $.message_link{color:<<messages.colors.good.link>>;}\n" +
						"$.message.good $.message_link:hover{color:<<messages.colors.good.link_hover>>;}\n" +
						"$.message.info $.message_link{color:<<messages.colors.info.link>>;}\n" +
						"$.message.info $.message_link:hover{color:<<messages.colors.info.link_hover>>;}\n" +

						"$.message_changes{max-height:200px;overflow-y:auto;margin-top:8px;}\n" +
						"$.message_changes.hidden{display:none;}\n" +
						"$.message_list{padding:0px;margin:0px 0px 0px 20px;list-style-type:disc;}\n" +
						"$.message_list_item{padding:0px;margin:0px;}\n" +
						"$.message_list_item+$.message_list_item{margin-top:2px;}\n" +
						"$.message_list_item+$.message_list_item.message_list_item_big_spacing{margin-top:10px;}\n" +
						//}

						//{ Messenger custom
						"$.message_progress{height:<<messages.progress_bar.height>>px;margin-top:<<messages.progress_bar.separation>>px;position:relative;display:block;width:100%;}\n" +
						"$.message_progress.hidden{display:none;}\n" +
						"$.message_progress.default{background-color:<<messages.progress_bar.colors.default>>;}\n" +
						"$.message_progress.active{background-color:<<messages.progress_bar.colors.active>>;}\n" +
						"$.message_progress_inner{position:absolute;left:0;top:0;bottom:0;width:0%;}\n" +
						"$.message_progress_inner.active{background-color:<<messages.progress_bar.colors.active>>;}\n" +
						//}

						//{ Banner
						"$.board_info{text-align:center;padding-bottom:<<board_info.padding.bottom>>px;}\n" +
						"$.board_banner_container{background-color:transparent;padding-bottom:<<board_info.banner.padding.bottom>>px;}\n" +
						"$.board_banner_container.error{display:none;}\n" +
						"$.board_banner{width:<<board_info.banner.size.width>>px;<<board_info.banner.size.height>>:100px;}\n" +
						"$.board_info_title{color:<<board_info.title.color>>;<<!font:board_info.title.font>>}\n" +
						"$.board_info_subtitle{color:<<board_info.subtitle.color>>;<<!font:board_info.subtitle.font>>}\n" +
						//}

						//{ Updater
						"$.updater{position:fixed;color:<<updater.colors.text>>;z-index:110;}\n" +
						"$.updater_small{position:relative;padding:<<updater.small.padding>>px;}\n" +
						"$.updater_small:before{content:\"<<!escape_string:updater.small.endings.start>>\";color:<<updater.small.endings.color>>;padding-right:<<updater.small.endings.spacing>>px;}\n" +
						"$.updater_small:after{content:\"<<!escape_string:updater.small.endings.end>>\";color:<<updater.small.endings.color>>;padding-left:<<updater.small.endings.spacing>>px;}\n" +
						"$.updater_small_item{vertical-align:middle;}\n" +

						"$.updater_counts.hidden," +
						"$.updater_counts_posts.hidden," +
						"$.updater_counts_images.hidden," +
						"$.updater_difference.hidden," +
						"$.updater_small>$.checkbox.hidden," +
						"$.updater_difference_removed.hidden{display:none;}\n" +

						"$.updater_counts_posts:not(.hidden)+$.updater_counts_images:not(.hidden):before{content:\"<<!escape_string:updater.symbols.separators.post_image>>\";}\n" +
						"$.updater_counts_posts:after{content:\"<<!escape_string:updater.symbols.post>>\";}\n" +
						"$.updater_counts_images:after{content:\"<<!escape_string:updater.symbols.image>>\";}\n" +
						"$.updater_counts_posts.limit," +
						"$.updater_counts_images.limit{color:<<updater.colors.limit>>;}\n" +
						"$.updater_counts.updater_totals>$.updater_counts_inner>$.posts+$.images:not(.hidden):before{color:<<updater.colors.text>>;}\n" +

						"$.updater_small>$.checkbox.enabled:not(.hidden)+$.updater_difference:not(.right)," +
						"$.updater_small>$.updater_difference:not(.right):not(.hidden)+$.updater_countdown," +
						"$.updater_small>$.checkbox.enabled:not(.hidden)+$.updater_difference:not(.right).hidden+$.updater_countdown," +
						"$.updater_small>$.checkbox.enabled:not(.hidden)+$.updater_difference.right+$.updater_countdown," +
						"$.updater_small>$.updater_difference.right," +
						"$.updater_small>$.updater_totals{margin-left:<<updater.small.spacing>>px;}\n" +

						"$.updater_small>$.updater_totals{cursor:move;}\n" +

						"$.updater_difference.right{float:right;}\n" +

						"$.updater_difference_added>$.updater_counts_inner:before{content:\"<<!escape_string:updater.symbols.added>>\";}\n" +
						"$.updater_difference_removed>$.updater_counts_inner:before{content:\"<<!escape_string:updater.symbols.removed>>\";}\n" +
						"$.updater_difference_added{color:<<updater.colors.added>>;}\n" +
						"$.updater_difference_removed{color:<<updater.colors.removed>>;}\n" +
						"$.updater_difference_added:not(.hidden)+$.updater_difference_removed:not(.hidden):before{content:\"<<!escape_string:updater.symbols.separators.added_removed>>\";color:<<updater.colors.text>>;}\n" +

						"$.updater_countdown{color:<<updater.colors.link>>;cursor:pointer;text-decoration:none;display:inline-block;position:relative;}\n" +
						"$.updater_countdown:hover{color:<<updater.colors.link_hover>>;}\n" +
						"$.updater_countdown.counting>$.updater_countdown_suffix:after{content:\"<<!escape_string:updater.symbols.countdown>>\";}\n" +

						"$.updater_countdown:not(.loading)>$.updater_progress{display:none;}\n" +
						"$.updater_progress{position:absolute;left:0px;top:0;right:0px;bottom:0;opacity:<<updater.small.progress.opacity>>;}\n" +
						"$.updater_progress.ajax{background-color:<<updater.small.progress.colors.ajax>>;}\n" +
						"$.updater_progress.parse{background-color:<<updater.small.progress.colors.parse>>;}\n" +
						"$.updater_progress_inner{position:absolute;left:0;top:0;width:0px;bottom:0;}\n" +
						"$.updater_progress_inner.ajax{background-color:<<updater.small.progress.colors.ajax>>;}\n" +
						"$.updater_progress_inner.parse{background-color:<<updater.small.progress.colors.parse>>;}\n" +

						"$.updater_background{position:absolute;left:0;top:0;bottom:0;right:0;background-color:<<updater.bg.color>>;opacity:<<updater.bg.opacity>>;box-shadow:<<!shadow:updater.bg.shadow>>;<<css.border-radius>>:<<updater.bg.radius>>px;}\n" +

						"$.updater.embedded{position:relative;z-index:auto;}\n" +
						"$.updater.embedded>$.updater_background{display:none;}\n" +
						"$.updater.embedded>$.updater_small{padding:0px;}\n" +
						"$.updater.embedded>$.updater_small>$.updater_small_item{vertical-align:baseline;}\n" +
						"$.updater.embedded>$.updater_small>$.updater_small_item.checkbox{vertical-align:middle;}\n" +
						//}

						//{ Overwrite styles
						"body>hr:nth-of-type(1){display:none;}\n" +
						"body>div.styles{display:none !important;position:absolute;}\n" +
						"body>div.pages{display:none;}\n" +
						"body>div.boardlist.bottom{display:none;}\n" +
						"body>p.unimportant{display:none;}\n" +
						"body>a:nth-of-type(1){display:none;}\n" +

						// Board list header
						"body>.boardlist:not(.bottom){margin:0px;padding:<<header.padding>>px;position:relative;left:0;top:0;right:0;z-index:100;<<!font:header.font>>}\n" +
						"body>.boardlist:not(.bottom):after{content:\"\";position:absolute;left:0;top:0;width:100%;height:100%;z-index:-1;opacity:<<header.background.opacity>>;box-shadow:<<!shadow:header.background.shadow>>;<<!gradient:header.background.gradient>>}\n" +
						"body>.boardlist:not(.bottom)>:not(a){display:none;}\n" +

						// Thread
						"body>form[name=\"postcontrols\"]{margin:0px;padding:<<thread.padding.top>>px <<thread.padding.right>>px <<thread.padding.bottom>>px <<thread.padding.left>>px;}\n" +
						"body>form[name=\"postcontrols\"]>div.delete{display:none;}\n" +
						"body>form[name=\"postcontrols\"]>div[id^=\"thread_\"]{margin:0px -<<thread.padding.left>>px 0px -<<thread.padding.right>>px;padding:<<thread.padding.top>>px <<thread.padding.right>>px <<thread.padding.bottom>>px <<thread.padding.left>>px;}\n" +
						"body>form[name=\"postcontrols\"]>hr," +
						"body>form[name=\"postcontrols\"]>div[id^=\"thread_\"]>hr{display:none;}\n" +
						"body>form[name=\"postcontrols\"]>br," +
						"body>form[name=\"postcontrols\"]>div[id^=\"thread_\"]>br," +
						"$.thread>br{display:none;}\n" +
						"body>form[name=\"postcontrols\"]>div+br," +
						"body>form[name=\"postcontrols\"]>div[id^=\"thread_\"]>div+br," +
						"$.thread>div+br{display:block;width:0px;height:0px;clear:both;}\n" +

						// Thread separator
						"body>form[name=\"postcontrols\"]>div[id^=\"thread_\"]+div[id^=\"thread_\"]{margin-top:<<thread.separator.height>>px;padding-top:<<thread.separator.size>>px;border-top:<<thread.separator.size>>px <<thread.separator.style>> <<thread.separator.color>>;}\n" +

						// Post
						"div.post:not($){margin:0px;padding:0px;margin:0px;padding:0px;max-width:none;color:<<post.colors.text>>;<<!font:post.font>>}\n" +
						"div.post.op:not($):after{clear:both;content:\"\";display:block;}\n" +
						"div.post.op:not($)+.post.reply:not($){margin-top:<<post.separation>>px;}\n" +
						"div.post.reply:not($){margin-top:<<post.separation>>px;background-color:<<post.colors.bg>>;border-width:<<post.border.top>>px <<post.border.right>>px <<post.border.bottom>>px <<post.border.left>>px;border-style:<<post.border.style>>;border-color:<<post.colors.border>>;<<css.border-radius>>:<<post.border.radius.topleft>>px <<post.border.radius.topright>>px <<post.border.radius.bottomright>>px <<post.border.radius.bottomleft>>px;}\n" +
						"div.post.reply.highlighted:not($){background-color:<<post.colors.bg_hl>>;border-color:<<post.colors.border_hl>>;}\n" +

						// File info
						"body>form[name=\"postcontrols\"] .fileinfo," +
						"$.content .fileinfo," +
						".post.reply:not($)>.fileinfo{padding:0px 0px <<post.file_info.padding.bottom>>px 0px;margin:0px;padding:0px 0px <<post.file_info.padding.bottom>>px 0px;visibility:hidden;<<!font:post.file_info.font>>}\n" +
						".post.reply:not($)>.fileinfo{padding:<<post.file_info.padding.top>>px <<post.file_info.padding.right>>px <<post.file_info.padding.bottom>>px <<post.file_info.padding.left>>px;margin-bottom:-<<post.comment.padding.top>>px;}\n" +
						"$.content .fileinfo>*," +
						"body>form[name=\"postcontrols\"] .fileinfo>*," +
						".post.reply:not($)>.fileinfo>*{visibility:visible;margin:0px;padding:0px;<<!font:post.file_info.file_extras.font>>}\n" +
						"$.content .fileinfo>.unimportant," +
						"body>form[name=\"postcontrols\"] .fileinfo>.unimportant," +
						".post.reply:not($)>.fileinfo>.unimportant{<<!font:post.file_info.attributes.font>>}\n" +
						"$.content .fileinfo>a:nth-of-type(1)," +
						"body>form[name=\"postcontrols\"] .fileinfo>a:nth-of-type(1)," +
						".post.reply:not($)>.fileinfo>a:nth-of-type(1){text-decoration:none;<<!font:post.file_info.file_name.font>>}\n" +

						// Header
						"div.post:not($)>.intro>*{vertical-align:middle;}\n" +
						"div.post:not($)>.intro>input{display:none;}\n" +
						"div.post.op:not($)>.intro{margin:0px;padding:0px <<post.header.padding.right>>px 0px 0px;display:inline-block;}\n" +
						"div.post.reply:not($)>.intro{margin:0px;padding:<<post.header.padding.top>>px <<post.header.padding.right>>px <<post.header.padding.bottom>>px <<post.header.padding.left>>px;background-color:<<post.header.colors.bg>>;border-width:<<post.header.border.top>>px <<post.header.border.right>>px <<post.header.border.bottom>>px <<post.header.border.left>>px;border-style:<<post.header.border.style>>;border-color:<<post.header.colors.border>>;<<css.border-radius>>:<<post.border.radius.topleft>>px <<post.border.radius.topright>>px 0px 0px;}\n" +
						"div.post.op:not($)>.intro>a:nth-of-type(3){margin-left:<<post.header.spacing>>px;text-decoration:none;color:<<post.header.options.colors.link>>;<<!font:post.header.options.font>>}\n" +
						"div.post.op:not($)>.intro>a:nth-of-type(3):hover{color:<<post.header.options.colors.link_hover>>;}\n" +
						"div.post.op:not($)>.intro>.icon{margin:0px 0px 0px <<post.header.spacing>>px;}\n" +

						// Comment
						"div.post:not($)>div.body{color:<<post.comment.colors.text>>;display:block;margin:0px;<<!font:post.comment.font>>}\n" +
						"div.post.op:not($)>div.body{padding:<<post.comment.padding.top>>px <<post.comment.padding.right>>px <<post.comment.padding.bottom>>px 0px;}\n" +
						"div.post.reply:not($)>div.body{padding:<<post.comment.padding.top>>px <<post.comment.padding.right>>px <<post.comment.padding.bottom>>px <<post.comment.padding.left>>px;}\n" +

						// Image
						"body>form[name=\"postcontrols\"] img:not(.icon):not($)," +
						"$.content img:not(.icon):not($){display:inline-block;float:left;padding:0px;background-color:<<post.image.op.bg>>;margin:0px <<post.comment.padding.left>>px 0px 0px;box-shadow:<<!shadow:post.image.op.thumbnail.shadow>>;}\n" +
						".post.reply img:not(.icon):not($){margin:<<post.comment.padding.top>>px <<post.comment.padding.left>>px <<post.comment.padding.bottom>>px <<post.image.padding.left>>px;background-color:<<post.image.reply.bg>>;box-shadow:<<!shadow:post.image.reply.thumbnail.shadow>>;}\n" +

						// Omitted replies / too long
						".post.op:not($)>.omitted," +
						".post:not($)>.body>.toolong{display:block;clear:both;text-decoration:none;color:<<post.notifications.colors.link>>;<<!font:post.notifications.font>>}\n" +
						".post.op:not($)>.omitted *," +
						".post:not($)>.body>.toolong *{text-decoration:none;color:<<post.notifications.colors.link>>;}\n" +
						".post.op:not($)>.omitted:before," +
						".post:not($)>.body>.toolong:before{content:\"<<!escape_string:post.notifications.separator.start>>\";color:<<post.notifications.colors.text>>;padding-right:<<post.notifications.spacing.inner>>px;}\n" +
						".post.op:not($)>.omitted:after," +
						".post:not($)>.body>.toolong:after{content:\"<<!escape_string:post.notifications.separator.end>>\";color:<<post.notifications.colors.text>>;padding-left:<<post.notifications.spacing.inner>>px;}\n" +


						//}

						//{ Special
						"*::selection{color:<<custom.selection>>;background-color:<<custom.selection_bg>>;}\n" +
						"*::-moz-selection{color:<<custom.selection>>;background-color:<<custom.selection_bg>>;}\n" +
						//}

						""
						/*!stylesheet-end!*/
						/*!stylesheet-pre-parse!*/)
					))
				); //}
			};

			var this_private = {

				parse_stylesheet_pre: function (ss) {
					return ss.replace(/\$/g, "." + this.cls);
				},
				parse_stylesheet: function (ss) {
					var self = this, i, val, fmt, p;

					return ss.replace(/<<([\s\S]+?)>>/gm, function (match, v) {
						fmt = null;
						// Check for formatter
						if (v[0] == '!') {
							p = v.indexOf(":");
							fmt = v.substr(1, p - 1);
							v = v.substr(p + 1);
						}
						// Function call
						if (fmt == "function") {
							p = v.indexOf(":");
							var fcn = v.substr(0, p);
							v = v.substr(p + 1);

							val = self[fcn].call(self, v);
						}
						else {
							// Variable get
							v = v.split(".");
							val = self;
							for (i = 0; i < v.length; ++i) {
								/*!debug!*/assert(val !== undefined, match);
								val = val[v[i]];
							}
							/*!debug!*/assert(val !== undefined, match);
							// Process variable
							if (fmt !== null) {
								if (fmt == "half") {
									/*!debug!*/assert(typeof(val) == typeof(0), match);
									val /= 2;
								}
								else if (fmt == "double") {
									/*!debug!*/assert(typeof(val) == typeof(0), match);
									val *= 2;
								}
								else if (fmt == "font") {
									p = "";
									if ("face" in val) p += "font-family:" + val.face + ";";
									if ("size" in val) p += "font-size:" + val.size + ";";
									if ("style" in val) p += "font-style:" + val.style + ";";
									if ("weight" in val) p += "font-weight:" + val.weight + ";";
									if ("decoration" in val) p += "text-decoration:" + val.decoration + ";";
									if ("spacing" in val) p += "letter-spacing:" + val.spacing + ";";
									val = p;
								}
								else if (fmt == "gradient") {
									var perc = [], css;
									for (v in val) {
										if (/^[0-9]+$/.test(v)) perc.push(v);
									}
									if (perc.length == 1) {
										// Single
										val = "background-color:" + val[perc[0]] + ";";
									}
									else {
										perc.sort(function (x, y) { return x - y; });
										if (val.direction == "t2b") {
											css = [
												val[perc[0]],
												"-moz-linear-gradient(top",
												"-webkit-gradient(linear,left top,left bottom",
												"-webkit-linear-gradient(top",
												"-o-linear-gradient(top",
												"-ms-linear-gradient(top",
												"linear-gradient(to bottom",
											];
										}
										else { // if (val.direction == "l2r") {
											css = [
												val[perc[0]],
												"-moz-linear-gradient(left",
												"-webkit-gradient(linear,left top,right top",
												"-webkit-linear-gradient(left",
												"-o-linear-gradient(left",
												"-ms-linear-gradient(left",
												"linear-gradient(to right",
											];
										}
										for (i = 0; i < perc.length; ++i) {
											v = "," + val[perc[i]] + " " + perc[i] + "%";
											css[1] += v;
											css[2] += ",color-stop(" + perc[i] + "%," + val[perc[i]] + ")";
											css[3] += v;
											css[4] += v;
											css[5] += v;
											css[6] += v;
										}
										val = "background:" + css[0] + ";";
										for (i = 1; i < css.length; ++i) val += "background:" + css[i] + ");"
									}
								}
								else if (fmt == "escape_string") {
									val = self.escape_string(val);
								}
								else if (fmt == "shadow") {
									var n = "";
									if (val.offset) {
										n += (val.offset.left || 0) + "px " + (val.offset.top || 0) + "px ";
									}
									else {
										n = "0px 0px ";
									}
									n += (val.blur || 0) + "px ";
									if ("size" in val) n += (val.size || 0) + "px ";
									n += val.color;
									if (val.inset) n += " inset";

									var f = 1;
									if ("factor" in val) f = val.factor;
									if (f <= 0) {
										val = "none";
									}
									else {
										val = n;
										for (i = 1; i < f; ++i) {
											val += ",";
											val += n;
										}
									}
								}
							}
						}
						return val;
					});
				}

			};

			Style.prototype = {

				constructor: Style,

				e: function (tag) {
					var e = document.createElement(tag);
					if (arguments.length <= 1) {
						e.className = this.cls;
					}
					else {
						tag = this.cls;
						for (var i = 1; i < arguments.length; ++i) {
							tag += " ";
							tag += arguments[i];
						}
						e.className = tag;
					}
					return $(e);
				},
				t: function (text) {
					return $(document.createTextNode(text));
				},

				add_class: function (obj) {
					obj.addClass(this.cls);
					return obj;
				},
				set_class: function (obj) {
					if (obj.length == 1) {
						if (arguments.length <= 1) {
							obj[0].className = this.cls;
						}
						else {
							var new_class = this.cls;
							for (var i = 1; i < arguments.length; ++i) {
								new_class += " ";
								new_class += arguments[i];
							}
							obj[0].className = new_class;
						}
					}
					else {
						var new_class = this.cls;
						if (arguments.length > 1) {
							for (var i = 1; i < arguments.length; ++i) {
								new_class += " ";
								new_class += arguments[i];
							}
						}
						for (var i = 0; i < obj.length; ++i) {
							obj[i].className = new_class;
						}
					}

					return obj;
				},

				stylize: function (html) {
					// Add style
					var body = html.find("body");
					body.addClass(this.cls);

					// Add gradient
					body.prepend(this.e("div").addClass("top_gradient"));
				},

				format_backlink_style: function (obj, reference) {
					var indicator = this.post.backlink_indicator;

					// Classing
					var to = {
						you: (content.posted_by_me(reference.origin.thread.board, reference.origin.thread.id, reference.origin.id)),
						self: (reference.origin.thread.board == reference.target_board && reference.origin.thread.id == reference.target_thread_id && reference.origin.id == reference.target_post_id),
						op: reference.origin.op,
						cross_thread: (reference.target_thread_id !== null && reference.origin.thread.id != reference.target_thread_id) || (reference.target_board != reference.origin.thread.board),
						dead: reference.origin.deleted,
						cross_board: reference.origin.thread.board != reference.target_board,
						past: false
					};
					to.past = !to.cross_thread && (reference.origin.id < reference.target_post_id);

					// Text
					var base = (to.cross_board ? indicator.cross_board : indicator.same_board);
					obj.text(
						xch.format_string(base, function (fmt, reference) {
							if (fmt == "post_id") fmt = reference.origin.id;
							else if (fmt == "board") fmt = reference.origin.thread.board;
							else if (fmt == "thread_id") fmt = reference.origin.thread.id;

							return fmt;
						}, reference)
					);

					// To-target
					var to_target = "";
					var suffix_list = (to.cross_board ? indicator.suffix_list.cross_board : indicator.suffix_list.same_board);
					for (var i = 0; i < suffix_list.length; ++i) {
						if (to[suffix_list[i][0]]) {
							if (to_target.length > 0) to_target += indicator.suffix_separator;
							to_target += suffix_list[i][1];
						}
					}
					if (to_target.length == 0) obj.removeAttr("to_target");
					else obj.attr("to_target", to_target);
				},
				format_reference_style: function (obj, reference) {
					var indicator = this.post.reference_indicator;

					// Classes
					var to = {
						you: (reference.target_thread_id !== null && content.posted_by_me(reference.target_board, reference.target_thread_id, reference.target_post_id)),
						self: (reference.origin.thread.board == reference.target_board && reference.origin.thread.id == reference.target_thread_id && reference.origin.id == reference.target_post_id),
						op: (reference.target != null && reference.target.op),
						cross_thread: (reference.target_thread_id !== null && reference.origin.thread.id != reference.target_thread_id) || (reference.target_board != reference.origin.thread.board),
						dead: (reference.target != null && reference.target.deleted) || reference.target_dead,
						cross_board: reference.origin.thread.board != reference.target_board,
						future: false
					};
					to.future = !to.cross_board && (reference.origin.id < reference.target_post_id);

					// Text
					var base = (to.cross_board ? indicator.cross_board : indicator.same_board);
					obj.text(
						xch.format_string(base, function (fmt, reference) {
							if (fmt == "post_id") fmt = reference.target_post_id;
							else if (fmt == "board") fmt = reference.target_board;
							else if (fmt == "thread_id") fmt = reference.target_thread_id || "";

							return fmt;
						}, reference)
					);

					// To-target
					var to_target = "";
					var suffix_list = (to.cross_board ? indicator.suffix_list.cross_board : indicator.suffix_list.same_board);
					for (var i = 0; i < suffix_list.length; ++i) {
						if (to[suffix_list[i][0]]) {
							if (to_target.length > 0) to_target += indicator.suffix_separator;
							to_target += suffix_list[i][1];
						}
					}
					if (to_target.length == 0) obj.removeAttr("to_target");
					else obj.attr("to_target", to_target);
				},

				checkbox: function (checked, size) {
					var s;
					if (!(size in this.checkbox_settings.sizes)) {
						size = "normal";
					}
					s = this.checkbox_settings.sizes[size];

					var sz = s.box_size - s.border_width * 2;

					return $(
						'<label class="' + this.cls + ' ' + size + ' checkbox">' +
						'<input class="' + this.cls + ' checkbox_input" type="checkbox"' + (checked ? ' checked="checked"' : '') + ' />' +
						'<span class="' + this.cls + ' checkbox_indicator">' +
						'<svg class="' + this.cls + ' checkbox_svg" xmlns="http://www.w3.org/2000/svg" version="1.1">' +
						'<g transform="scale(' + sz + ',' + sz + ')">' +
						'<polygon points="0.75,0.0625 0.9375,0.1875 0.5,0.9375 0.3125,0.9375 0.0625,0.6875 0.1875,0.5 0.375,0.6875"></polygon>' +
						'</g>' +
						'</svg>' +
						'</span>' +
						'</label>'
					);
				},
				radiobox: function (checked, size) {
					var s;
					if (!(size in this.checkbox_settings.sizes)) {
						size = "normal";
					}
					s = this.checkbox_settings.sizes[size];

					var sz = s.box_size;

					return $(
						'<label class="' + this.cls + ' ' + size + ' radiobox">' +
						'<input class="' + this.cls + ' radiobox_input" type="checkbox"' + (checked ? ' checked="checked"' : '') + ' />' +
						'<span class="' + this.cls + ' radiobox_indicator">' +
						'<svg class="' + this.cls + ' radiobox_svg" xmlns="http://www.w3.org/2000/svg" version="1.1">' +
						'<g transform="scale(' + sz + ',' + sz + ')" class="' + this.cls + ' radiobox_svg_g">' +
						'<circle cx="0.5" cy="0.5" r="0.5" class="' + this.cls + ' radiobox_svg_border"></circle>' +
						'<circle cx="0.5" cy="0.5" r="0.4" class="' + this.cls + ' radiobox_svg_bg"></circle>' +
						'<circle cx="0.5" cy="0.5" r="0.3" class="' + this.cls + ' radiobox_svg_tick"></circle>' +
						'</g>' +
						'</svg>' +
						'</span>' +
						'</label>'
					);
				},
				escape_string: function (str) {
					return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
				},

				remove: function () {
					this.style_tag.remove();
				}

			};

			Style.CSS = (function () {

				var CSS = function () {
					this.j = {
						transform: "transform",
						transformOrigin: "transformOrigin",
						transformStyle: "transformStyle",
						transition: "transition",
						perspective: "perspective",
						perspectiveOrigin: "perspectiveOrigin",
						backfaceVisibility: "backfaceVisibility",
						borderRadius: "borderRadius",
						borderTopLeftRadius: "borderTopLeftRadius",
						borderTopRightRadius: "borderTopRightRadius",
						borderBottomRightRadius: "borderBottomRightRadius",
						borderBottomLeftRadius: "borderBottomLeftRadius",
						boxSizing: "boxSizing"
					};
					this.events = {
						transitionend: "transitionend"
					};

					var e = document.createElement("div");
					var prefixes = [ "" , "webkit" , "Moz" , "O" , "ms" ];
					var prefixes2 = [ "" , "-webkit-" , "-moz-" , "-o-" , "-ms-" ];

					for (var key in this.j) {
						var key2 = key;
						for (var i = 0; i < prefixes.length; ++i) {
							if (typeof(e.style[prefixes[i] + key2]) == typeof("")) {
								this.j[key] = prefixes[i] + key2;

								var k2 = key.replace(/([A-Z])/g, function (a, b, c) {
									return "-" + a.toLowerCase();
								});
								this[k2] = prefixes2[i] + k2;

								break;
							}
							if (i == 0) key2 = key.substr(0, 1).toUpperCase() + key.substr(1);
						}
					}

					if (this.j.transition != "transition") {
						this.events.transitionend = this.j.transition + "End";
					}
				};

				return CSS;

			})();

			return Style;

		})()

	};

	// Done
	return xch;

})();



xch.script.startup();
////////////////////////////////////////////////////////////////////////////////
//} /Userscript
////////////////////////////////////////////////////////////////////////////////