4chan W

By Pseudo Last update Feb 14, 2012 — Installed 5,266 times.

There are 2 previous versions of this script.

the source is over 100KB, syntax highlighting in the browser is too slow

// ==UserScript==
// @name           4chan W
// @version        0.1.4
// @namespace      http://pseudochron.com
// @description    Enhances 4chan by adding various features.
// @include        http://boards.4chan.org/*
// @include        http://images.4chan.org/*
// @include        http://sys.4chan.org/*
// @run-at         document-start
// @updateURL      http://userscripts.org/scripts/source/125646.meta.js
// @icon           http://pseudochron.com/4chan-w/4chan_w_icon32.png
// ==/UserScript==

/* LICENSE
 *
 * 4chan W is a fork of the MIT-licensed 4chan X.
 * 
 * 4chan X Copyright (c) James Campos and Nicolas Stepien
 * 4chan X: http://mayhemydg.github.com/4chan-x/
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 */

(function() {
  var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, Recaptcha, SECOND, Time, VERSION, anonymize, conf, config, cooldown, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, nav, options, qr, quoteBacklink, quoteDR, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base,
	__slice = Array.prototype.slice;

  config = {
	main: {
	  Enhancing: {
		'404 Redirect': [true, 'Redirect dead threads and images'],
		'Keybinds': [false, 'Binds actions to keys'],
		'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'],
		'Report Button': [true, 'Add report buttons'],
		'Comment Expansion': [true, 'Expand too long comments'],
		'Thread Expansion': [true, 'View all replies'],
		'Index Navigation': [true, 'Navigate to previous / next thread'],
		'Reply Navigation': [false, 'Navigate to top / bottom of thread'],
		'Style Buttons': [true, 'Style \'hide\' and \'report\'  buttons'],
		'Top on Refresh': [true, 'Scrolls to the top after refreshing a frontpage']/* ,
		'Check for Updates': [false, 'Check for updated versions of 4chan W']*/
	  },
	  Filtering: {
		'Anonymize': [false, 'Make everybody anonymous'],
		'Filter': [false, 'Hide posts matching the filter'],
		'Filter OPs': [false, 'Filter OPs along with their threads'],
		'Reply Hiding': [false, 'Hide single replies'],
		'Thread Hiding': [true, 'Hide entire threads'],
		'Show Stubs': [true, 'Of hidden threads / replies']
	  },
	  Imaging: {
		'Image Auto-Gif': [false, 'Animate gif thumbnails'],
		'Image Expansion': [true, 'Expand images'],
		'Image Hover': [false, 'Show full image on mouseover'],
		'Sauce': [true, 'Add links to search for the source of images'],
		'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'],
		'Image Preloading': [false, 'Add a button to preload images']
	  },
	  Monitoring: {
		'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'],
		'Unread Count': [true, 'Show unread post count in tab icon'],
//         'Unread Count in Title': [true, 'Show unread post count in tab title'],
		'Post in Title': [true, 'Show the op\'s post in the tab title'],
		'Thread Stats': [true, 'Display reply and image count'],
		'Thread Watcher': [false, 'Bookmark threads'],
		'Auto Watch': [true, 'Automatically watch threads that you start'],
		'Auto Watch Reply': [false, 'Automatically watch threads that you reply to']
	  },
	  Posting: {
		'Auto Noko': [true, 'Always redirect to your post'],
		'Cooldown': [true, 'Prevent \'flood detected\' errors'],
		'Quick Reply': [true, 'Reply without leaving the page'],
		'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.'],
		'Auto Hide QR': [true, 'Automatically auto-hide the quick reply when posting'],
		'Relocate Text': [true, 'Quick reply copies and removes any text from the standard comment box'],
		'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting']
	  },
	  Quoting: {
		'Quote Backlinks': [true, 'Add quote backlinks'],
		'OP Backlinks': [false, 'Add backlinks to the OP'],
		'Quote Highlighting': [true, 'Highlight the previewed post'],
		'Quote Inline': [true, 'Show quoted post inline on quote click'],
		'Quote Preview': [true, 'Show quote content on hover'],
		'Indicate OP quote': [true, 'Add \'(OP)\' to OP quotes'],
		'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'],
		'Forward Hiding': [true, 'Hide original posts of inlined backlinks']
	  }
	},
	filter: {
	  name: '',
	  tripcode: '',
	  email: '',
	  subject: '',
	  comment: '',
	  filename: '',
	  filesize: '',
	  md5: ''
	},
	flavors: ['http://google.com/searchbyimage?image_url=', '#http://tineye.com/search?url=', '#http://iqdb.org/?url=', '#http://saucenao.com/search.php?db=999&url=', '#http://3d.iqdb.org/?url=', '#http://regex.info/exif.cgi?imgurl=', '#http://imgur.com/upload?url=', '#http://ompldr.org/upload?url1='].join('\n'),
	time: '%m/%d/%y(%a)%H:%M',
	backlink: '>>%id',
	favicon: 'ferongr',
	hotkeys: {
	  openOptions: 'ctrl+o',
	  close: 'Esc',
	  spoiler: 'ctrl+s',
	  openQR: 'i',
	  openEmptyQR: 'I',
	  submit: 'alt+s',
	  nextReply: 'J',
	  previousReply: 'K',
	  nextThread: 'n',
	  previousThread: 'p',
	  nextPage: 'L',
	  previousPage: 'H',
	  zero: '0',
	  openThreadTab: 'o',
	  openThread: 'O',
	  expandThread: 'e',
	  watch: 'w',
	  hide: 'x',
	  expandImages: 'm',
	  expandAllImages: 'M',
	  update: 'u',
	  unreadCountTo0: 'z'
	},
	updater: {
	  checkbox: {
		'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'],
		'Scroll BG': [false, 'Scroll background tabs'],
		'Verbose': [true, 'Show countdown timer, new post count'],
		'Auto Update': [true, 'Automatically fetch new posts']
	  },
	  'Interval': 45
	}
  };

  log = typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0;

  conf = {};

  (flatten = function(parent, obj) {
	var key, val, _results;
	if (obj.length) {
	  if (typeof obj[0] === 'boolean') {
		return conf[parent] = obj[0];
	  } else {
		return conf[parent] = obj;
	  }
	} else if (typeof obj === 'object') {
	  _results = [];
	  for (key in obj) {
		val = obj[key];
		_results.push(flatten(key, val));
	  }
	  return _results;
	} else {
	  return conf[parent] = obj;
	}
  })(null, config);

  NAMESPACE = '4chan_W.';

  VERSION = '0.1.4';

  SECOND = 1000;

  MINUTE = 60 * SECOND;

  HOUR = 60 * MINUTE;

  DAY = 24 * HOUR;

  engine = /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase();

  d = document;

  g = {
	callbacks: []
  };

  ui = {
	dialog: function(id, position, html) {
	  var el, saved, _ref;
	  el = d.createElement('div');
	  el.className = 'reply dialog';
	  el.innerHTML = html;
	  el.id = id;
	  el.style.cssText = (saved = localStorage["" + NAMESPACE + id + ".position"]) ? saved : position;
	  if ((_ref = el.querySelector('div.move')) != null) {
		_ref.addEventListener('mousedown', ui.dragstart, false);
	  }
	  return el;
	},
	dragstart: function(e) {
	  var el, rect;
	  e.preventDefault();
	  ui.el = el = this.parentNode;
	  d.addEventListener('mousemove', ui.drag, false);
	  d.addEventListener('mouseup', ui.dragend, false);
	  rect = el.getBoundingClientRect();
	  ui.dx = e.clientX - rect.left;
	  ui.dy = e.clientY - rect.top;
	  ui.width = d.body.clientWidth - el.offsetWidth;
	  return ui.height = d.body.clientHeight - el.offsetHeight;
	},
	drag: function(e) {
	  var bottom, left, right, style, top;
	  left = e.clientX - ui.dx;
	  top = e.clientY - ui.dy;
	  left = left < 10 ? 0 : ui.width - left < 10 ? null : left;
	  top = top < 10 ? 0 : ui.height - top < 10 ? null : top;
	  right = left === null ? 0 : null;
	  bottom = top === null ? 0 : null;
	  style = ui.el.style;
	  style.top = top;
	  style.right = right;
	  style.bottom = bottom;
	  return style.left = left;
	},
	dragend: function() {
	  var el;
	  el = ui.el;
	  localStorage["" + NAMESPACE + el.id + ".position"] = el.style.cssText;
	  d.removeEventListener('mousemove', ui.drag, false);
	  return d.removeEventListener('mouseup', ui.dragend, false);
	},
	hover: function(e) {
	  var clientHeight, clientWidth, clientX, clientY, el, height, style, top, _ref;
	  clientX = e.clientX, clientY = e.clientY;
	  el = ui.el;
	  style = el.style;
	  _ref = d.body, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth;
	  height = el.offsetHeight;
	  top = clientY - 120;
	  style.top = clientHeight < height || top < 0 ? 0 : top + height > clientHeight ? clientHeight - height : top;
	  if (clientX < clientWidth - 400) {
		style.left = clientX + 45;
		return style.right = null;
	  } else {
		style.left = null;
		return style.right = clientWidth - clientX + 45;
	  }
	},
	hoverend: function() {
	  return ui.el.parentNode.removeChild(ui.el);
	}
  };

  /*
  loosely follows the jquery api:
  http://api.jquery.com/
  not chainable
  */

  $ = function(selector, root) {
	if (root == null) root = d.body;
	return root.querySelector(selector);
  };

  $.extend = function(object, properties) {
	var key, val;
	for (key in properties) {
	  val = properties[key];
	  object[key] = val;
	}
	return object;
  };

  $.extend($, {
	ready: function(fc) {
	  var cb;
	  if (/interactive|complete/.test(d.readyState)) return setTimeout(fc);
	  cb = function() {
		$.off(d, 'DOMContentLoaded', cb);
		return fc();
	  };
	  return $.on(d, 'DOMContentLoaded', cb);
	},
	id: function(id) {
	  return d.getElementById(id);
	},
	globalEval: function(code) {
	  var script;
	  script = $.el('script', {
		textContent: "(" + code + ")()"
	  });
	  $.add(d.head, script);
	  return $.rm(script);
	},
	ajax: function(url, cb, opts) {
	  var event, headers, key, r, type, val;
	  if (opts == null) opts = {};
	  type = opts.type, event = opts.event, headers = opts.headers;
	  type || (type = 'get');
	  event || (event = 'onload');
	  r = new XMLHttpRequest();
	  r.open(type, url, true);
	  for (key in headers) {
		val = headers[key];
		r.setRequestHeader(key, val);
	  }
	  r[event] = cb;
	  r.send();
	  return r;
	},
	cache: function(url, cb) {
	  var req;
	  if (req = $.cache.requests[url]) {
		if (req.readyState === 4) {
		  return cb.call(req);
		} else {
		  return req.callbacks.push(cb);
		}
	  } else {
		req = $.ajax(url, (function() {
		  var cb, _i, _len, _ref, _results;
		  _ref = this.callbacks;
		  _results = [];
		  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
			cb = _ref[_i];
			_results.push(cb.call(this));
		  }
		  return _results;
		}));
		req.callbacks = [cb];
		return $.cache.requests[url] = req;
	  }
	},
	cb: {
	  checked: function() {
		$.set(this.name, this.checked);
		return conf[this.name] = this.checked;
	  },
	  value: function() {
		$.set(this.name, this.value);
		return conf[this.name] = this.value;
	  }
	},
	addStyle: function(css) {
	  var style;
	  style = $.el('style', {
		textContent: css
	  });
	  $.add(d.head, style);
	  return style;
	},
	x: function(path, root) {
	  if (root == null) root = d.body;
	  return d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue;
	},
	tn: function(s) {
	  return d.createTextNode(s);
	},
	replace: function(root, el) {
	  return root.parentNode.replaceChild(el, root);
	},
	addClass: function(el, className) {
	  return el.classList.add(className);
	},
	removeClass: function(el, className) {
	  return el.classList.remove(className);
	},
	rm: function(el) {
	  return el.parentNode.removeChild(el);
	},
	add: function() {
	  var child, children, parent, _i, _len, _results;
	  parent = arguments[0], children = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
	  _results = [];
	  for (_i = 0, _len = children.length; _i < _len; _i++) {
		child = children[_i];
		_results.push(parent.appendChild(child));
	  }
	  return _results;
	},
	prepend: function(parent, child) {
	  return parent.insertBefore(child, parent.firstChild);
	},
	after: function(root, el) {
	  return root.parentNode.insertBefore(el, root.nextSibling);
	},
	before: function(root, el) {
	  return root.parentNode.insertBefore(el, root);
	},
	el: function(tag, properties) {
	  var el;
	  el = d.createElement(tag);
	  if (properties) $.extend(el, properties);
	  return el;
	},
	on: function(el, eventType, handler) {
	  return el.addEventListener(eventType, handler, false);
	},
	off: function(el, eventType, handler) {
	  return el.removeEventListener(eventType, handler, false);
	},
	isDST: function() {
	  /*
			http://en.wikipedia.org/wiki/Eastern_Time_Zone
			Its UTC time offset is −5 hrs (UTC−05) during standard time and −4
			hrs (UTC−04) during daylight saving time.
	  
			Since 2007, the local time changes at 02:00 EST to 03:00 EDT on the second
			Sunday in March and returns at 02:00 EDT to 01:00 EST on the first Sunday
			in November, in the U.S. as well as in Canada.
	  
			0200 EST (UTC-05) = 0700 UTC
			0200 EDT (UTC-04) = 0600 UTC
	  */
	  var D, date, day, hours, month, sunday;
	  D = new Date();
	  date = D.getUTCDate();
	  day = D.getUTCDay();
	  hours = D.getUTCHours();
	  month = D.getUTCMonth();
	  if (month < 2 || 10 < month) return false;
	  if ((2 < month && month < 10)) return true;
	  sunday = date - day;
	  if (month === 2) {
		if (sunday < 8) return false;
		if (sunday < 15 && day === 0) {
		  if (hours < 7) return false;
		  return true;
		}
		return true;
	  }
	  if (sunday < 1) return true;
	  if (sunday < 8 && day === 0) {
		if (hours < 6) return true;
		return false;
	  }
	  return false;
	}
  });

  $.cache.requests = {};

  $.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? {
	"delete": function(name) {
	  name = NAMESPACE + name;
	  return GM_deleteValue(name);
	},
	get: function(name, defaultValue) {
	  var value;
	  name = NAMESPACE + name;
	  if (value = GM_getValue(name)) {
		return JSON.parse(value);
	  } else {
		return defaultValue;
	  }
	},
	set: function(name, value) {
	  name = NAMESPACE + name;
	  localStorage[name] = JSON.stringify(value);
	  return GM_setValue(name, JSON.stringify(value));
	}
  } : {
	"delete": function(name) {
	  name = NAMESPACE + name;
	  return delete localStorage[name];
	},
	get: function(name, defaultValue) {
	  var value;
	  name = NAMESPACE + name;
	  if (value = localStorage[name]) {
		return JSON.parse(value);
	  } else {
		return defaultValue;
	  }
	},
	set: function(name, value) {
	  name = NAMESPACE + name;
	  return localStorage[name] = JSON.stringify(value);
	}
  });

  for (key in conf) {
	val = conf[key];
	conf[key] = $.get(key, val);
  }

  $$ = function(selector, root) {
	if (root == null) root = d.body;
	return Array.prototype.slice.call(root.querySelectorAll(selector));
  };

  filter = {
	regexps: {},
	callbacks: [],
	init: function() {
	  var f, filter, key, m, _i, _len;
	  for (key in config.filter) {
		if (!(m = conf[key].match(/^\/.+\/\w*$/gm))) continue;
		this.regexps[key] = [];
		for (_i = 0, _len = m.length; _i < _len; _i++) {
		  filter = m[_i];
		  f = filter.match(/^\/(.+)\/(\w*)$/);
		  try {
			this.regexps[key].push(RegExp(f[1], f[2]));
		  } catch (e) {
			alert(e.message);
		  }
		}
		this.callbacks.push(this[key]);
	  }
	  return g.callbacks.push(this.node);
	},
	node: function(root) {
	  if (!root.className) {
		if (filter.callbacks.some(function(callback) {
		  return callback(root);
		})) {
		  return replyHiding.hideHide($('td:not([nowrap])', root));
		}
	  } else if (root.className === 'op' && !g.REPLY && conf['Filter OPs']) {
		if (filter.callbacks.some(function(callback) {
		  return callback(root);
		})) {
		  return threadHiding.hideHide(root.parentNode);
		}
	  }
	},
	test: function(key, value) {
	  return filter.regexps[key].some(function(regexp) {
		return regexp.test(value);
	  });
	},
	name: function(root) {
	  var name;
	  name = root.className === 'op' ? $('.postername', root) : $('.commentpostername', root);
	  return filter.test('name', name.textContent);
	},
	tripcode: function(root) {
	  var trip;
	  if (trip = $('.postertrip', root)) {
		return filter.test('tripcode', trip.textContent);
	  }
	},
	email: function(root) {
	  var mail;
	  if (mail = $('.linkmail', root)) return filter.test('email', mail.href);
	},
	subject: function(root) {
	  var sub;
	  sub = root.className === 'op' ? $('.filetitle', root) : $('.replytitle', root);
	  return filter.test('subject', sub.textContent);
	},
	comment: function(root) {
	  return filter.test('comment', ($.el('a', {
		innerHTML: $('blockquote', root).innerHTML.replace(/<br>/g, '\n')
	  })).textContent);
	},
	filename: function(root) {
	  var file;
	  if (file = $('.filesize span', root)) {
		return filter.test('filename', file.title);
	  }
	},
	filesize: function(root) {
	  var img;
	  if (img = $('img[md5]', root)) return filter.test('filesize', img.alt);
	},
	md5: function(root) {
	  var img;
	  if (img = $('img[md5]', root)) {
		return filter.test('md5', img.getAttribute('md5'));
	  }
	}
  };

  strikethroughQuotes = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var el, quote, _i, _len, _ref, _results;
		if (root.className === 'inline') return;
		_ref = $$('.quotelink', root);
		_results = [];
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (el = $.id(quote.hash.slice(1))) {
			if (el.parentNode.parentNode.parentNode.hidden) {
			  _results.push($.addClass(quote, 'filtered'));
			} else {
			  _results.push(void 0);
			}
		  } else {
			_results.push(void 0);
		  }
		}
		return _results;
	  });
	}
  };

  expandComment = {
	init: function() {
	  var a, _i, _len, _ref, _results;
	  _ref = $$('.abbr a');
	  _results = [];
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		a = _ref[_i];
		_results.push($.on(a, 'click', expandComment.expand));
	  }
	  return _results;
	},
	expand: function(e) {
	  var a, replyID, threadID, _, _ref;
	  e.preventDefault();
	  _ref = this.href.match(/(\d+)#(\d+)/), _ = _ref[0], threadID = _ref[1], replyID = _ref[2];
	  this.textContent = "Loading " + replyID + "...";
	  threadID = this.pathname.split('/').pop() || $.x('ancestor::div[@class="thread"]/div', this).id;
	  a = this;
	  return $.cache(this.pathname, (function() {
		return expandComment.parse(this, a, threadID, replyID);
	  }));
	},
	parse: function(req, a, threadID, replyID) {
	  var body, bq, quote, reply, _i, _j, _len, _len2, _ref, _ref2;
	  if (req.status !== 200) {
		a.textContent = "" + req.status + " " + req.statusText;
		return;
	  }
	  body = $.el('body', {
		innerHTML: req.responseText
	  });
	  if (threadID === replyID) {
		bq = $('blockquote', body);
	  } else {
		_ref = $$('td[id]', body);
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  reply = _ref[_i];
		  if (reply.id === replyID) {
			bq = $('blockquote', reply);
			break;
		  }
		}
	  }
	  _ref2 = $$('.quotelink', bq);
	  for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
		quote = _ref2[_j];
		if (quote.getAttribute('href') === quote.hash) {
		  quote.pathname = "/" + g.BOARD + "/res/" + threadID;
		}
		if (quote.hash.slice(1) === threadID) quote.innerHTML += '&nbsp;(OP)';
		if (conf['Quote Preview']) {
		  $.on(quote, 'mouseover', quotePreview.mouseover);
		  $.on(quote, 'mousemove', ui.hover);
		  $.on(quote, 'mouseout', quotePreview.mouseout);
		}
		if (conf['Quote Inline']) $.on(quote, 'click', quoteInline.toggle);
	  }
	  return $.replace(a.parentNode.parentNode, bq);
	}
  };

  expandThread = {
	init: function() {
	  var a, span, _i, _len, _ref, _results;
	  _ref = $$('.omittedposts');
	  _results = [];
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		span = _ref[_i];
		a = $.el('a', {
		  textContent: "+ " + span.textContent,
		  className: 'omittedposts',
		  href: 'javascript:;'
		});
		$.on(a, 'click', expandThread.cb.toggle);
		_results.push($.replace(span, a));
	  }
	  return _results;
	},
	cb: {
	  toggle: function() {
		var thread;
		thread = this.parentNode;
		return expandThread.toggle(thread);
	  }
	},
	toggle: function(thread) {
	  var a, backlink, num, pathname, prev, table, threadID, _i, _len, _ref, _ref2, _results;
	  threadID = thread.firstChild.id;
	  pathname = "/" + g.BOARD + "/res/" + threadID;
	  a = $('.omittedposts', thread);
	  switch (a.textContent[0]) {
		case '+':
		  if ((_ref = $('.op .container', thread)) != null) _ref.innerHTML = '';
		  a.textContent = a.textContent.replace('+', 'X Loading...');
		  return $.cache(pathname, (function() {
			return expandThread.parse(this, pathname, thread, a);
		  }));
		case 'X':
		  a.textContent = a.textContent.replace('X Loading...', '+');
		  return $.cache[pathname].abort();
		case '-':
		  a.textContent = a.textContent.replace('-', '+');
		  num = (function() {
			switch (g.BOARD) {
			  case 'b':
				return 3;
			  case 't':
				return 1;
			  default:
				return 5;
			}
		  })();
		  table = $.x("following::br[@clear]/preceding::table[" + num + "]", a);
		  while ((prev = table.previousSibling) && (prev.nodeName === 'TABLE')) {
			$.rm(prev);
		  }
		  _ref2 = $$('.op a.backlink');
		  _results = [];
		  for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
			backlink = _ref2[_i];
			if (!$.id(backlink.hash.slice(1))) {
			  _results.push($.rm(backlink));
			} else {
			  _results.push(void 0);
			}
		  }
		  return _results;
	  }
	},
	parse: function(req, pathname, thread, a) {
	  var body, frag, href, link, next, quote, reply, _i, _j, _len, _len2, _ref, _ref2;
	  if (req.status !== 200) {
		a.textContent = "" + req.status + " " + req.statusText;
		$.off(a, 'click', expandThread.cb.toggle);
		return;
	  }
	  a.textContent = a.textContent.replace('X Loading...', '-');
	  body = $.el('body', {
		innerHTML: req.responseText
	  });
	  frag = d.createDocumentFragment();
	  _ref = $$('.reply', body);
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		reply = _ref[_i];
		_ref2 = $$('.quotelink', reply);
		for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
		  quote = _ref2[_j];
		  if ((href = quote.getAttribute('href')) === quote.hash) {
			quote.pathname = pathname;
		  } else if (href !== quote.href) {
			quote.href = "res/" + href;
		  }
		}
		link = $('.quotejs', reply);
		link.href = "res/" + thread.firstChild.id + "#" + reply.id;
		link.nextSibling.href = "res/" + thread.firstChild.id + "#q" + reply.id;
		$.add(frag, reply.parentNode.parentNode.parentNode);
	  }
	  while ((next = a.nextSibling) && !next.clear) {
		$.rm(next);
	  }
	  return $.before(next, frag);
	}
  };

  replyHiding = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var a, dd, id, reply;
		if (!(dd = $('.doubledash', root))) return;
		dd.className = 'replyhider';
		if (conf['Style Buttons']) {
			a = $.el('a', {
			  textContent: '--',
			  className: 'postblock buttons replyhide',
			  href: 'javascript:;'
			});	
		} else {
			a = $.el('a', {
			  textContent: '[ - ]',
			  href: 'javascript:;'
			});	
		}

		$.on(a, 'click', replyHiding.cb.hide);
		$.replace(dd.firstChild, a);
		reply = dd.nextSibling;
		id = reply.id;
		if (id in g.hiddenReplies) return replyHiding.hide(reply);
	  });
	},
	cb: {
	  hide: function() {
		var reply;
		reply = this.parentNode.nextSibling;
		return replyHiding.hide(reply);
	  },
	  show: function() {
		var div, table;
		div = this.parentNode;
		table = div.nextSibling;
		replyHiding.show(table);
		return $.rm(div);
	  }
	},
	hide: function(reply) {
	  var id, quote, _i, _len, _ref;
	  replyHiding.hideHide(reply);
	  id = reply.id;
	  _ref = $$(".quotelink[href='#" + id + "'], .backlink[href='#" + id + "']");
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		quote = _ref[_i];
		$.addClass(quote, 'filtered');
	  }
	  g.hiddenReplies[id] = Date.now();
	  return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
	},
	hideHide: function(reply) {
	  var a, div, name, table, trip, _ref;
	  table = reply.parentNode.parentNode.parentNode;
	  if (table.hidden) return;
	  table.hidden = true;
	  if (conf['Show Stubs']) {
		name = $('.commentpostername', reply).textContent;
		trip = ((_ref = $('.postertrip', reply)) != null ? _ref.textContent : void 0) || '';
		if (conf['Style Buttons']) {
			a = $.el('a', {
			  innerHTML: "<span class='postblock buttons replyhide' style='padding-left: 3px; padding-right: 3px;'>+</span> " + name + " " + trip,
			  href: 'javascript:;'
			});
		} else {
			a = $.el('a', {
	 		  innerHTML: "<span>[ + ]</span> " + name + " " + trip,
			  href: 'javascript:;'
			});
		}
		
		$.on(a, 'click', replyHiding.cb.show);
		div = $.el('div', {
		  className: 'stub'
		});
		$.add(div, a);
		return $.before(table, div);
	  }
	},
	show: function(table) {
	  var id, quote, _i, _len, _ref;
	  table.hidden = false;
	  id = $('td[id]', table).id;
	  _ref = $$(".quotelink[href='#" + id + "'], .backlink[href='#" + id + "']");
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		quote = _ref[_i];
		$.removeClass(quote, 'filtered');
	  }
	  delete g.hiddenReplies[id];
	  return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
	}
  };

  keybinds = {
	init: function() {
	  var node, _i, _len, _ref;
	  _ref = $$('[accesskey]');
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		node = _ref[_i];
		node.removeAttribute('accesskey');
	  }
	  return $.on(d, 'keydown', keybinds.keydown);
	},
	keydown: function(e) {
	  var o, range, selEnd, selStart, ta, thread, valEnd, valMid, valStart, value, _ref, _ref2, _ref3;
	  updater.focus = true;
	  if (((_ref = e.target.nodeName) === 'TEXTAREA' || _ref === 'INPUT') && !e.altKey && !e.ctrlKey && !(e.keyCode === 27)) {
		return;
	  }
	  if (!(key = keybinds.keyCode(e))) return;
	  thread = nav.getThread();
	  switch (key) {
		case conf.openOptions:
		  if (!$.id('overlay')) options.dialog();
		  break;
		case conf.close:
		  if (o = $.id('overlay')) {
			$.rm(o);
		  } else if (qr.el) {
			qr.close();
		  }
		  break;
		case conf.spoiler:
		  ta = e.target;
		  if (ta.nodeName !== 'TEXTAREA') return;
		  value = ta.value;
		  selStart = ta.selectionStart;
		  selEnd = ta.selectionEnd;
		  valStart = value.slice(0, selStart) + '[spoiler]';
		  valMid = value.slice(selStart, selEnd);
		  valEnd = '[/spoiler]' + value.slice(selEnd);
		  ta.value = valStart + valMid + valEnd;
		  range = valStart.length + valMid.length;
		  ta.setSelectionRange(range, range);
		  break;
		case conf.zero:
		  window.location = "/" + g.BOARD + "/0#0";
		  break;
		case conf.openEmptyQR:
		  keybinds.qr(thread);
		  break;
		case conf.nextReply:
		  keybinds.hl.next(thread);
		  break;
		case conf.previousReply:
		  keybinds.hl.prev(thread);
		  break;
		case conf.expandAllImages:
		  keybinds.img(thread, true);
		  break;
		case conf.openThread:
		  keybinds.open(thread);
		  break;
		case conf.expandThread:
		  expandThread.toggle(thread);
		  break;
		case conf.openQR:
		  keybinds.qr(thread, true);
		  break;
		case conf.expandImages:
		  keybinds.img(thread);
		  break;
		case conf.nextThread:
		  if (g.REPLY) return;
		  nav.scroll(+1);
		  break;
		case conf.openThreadTab:
		  keybinds.open(thread, true);
		  break;
		case conf.previousThread:
		  if (g.REPLY) return;
		  nav.scroll(-1);
		  break;
		case conf.update:
		  updater.update();
		  break;
		case conf.watch:
		  watcher.toggle(thread);
		  break;
		case conf.hide:
		  threadHiding.toggle(thread);
		  break;
		case conf.nextPage:
		  if ((_ref2 = $('input[value=Next]')) != null) _ref2.click();
		  break;
		case conf.previousPage:
		  if ((_ref3 = $('input[value=Previous]')) != null) _ref3.click();
		  break;
		case conf.submit:
		  if (qr.el) {
			qr.submit.call($('form', qr.el));
		  } else {
			$('.postarea form').submit();
		  }
		  break;
		case conf.unreadCountTo0:
		  unread.replies = [];
		  unread.updateTitle();
		  Favicon.update();
		  break;
		default:
		  return;
	  }
	  return e.preventDefault();
	},
	keyCode: function(e) {
	  var c, kc;
	  key = (function() {
		switch (kc = e.keyCode) {
		  case 8:
			return '';
		  case 27:
			return 'Esc';
		  case 37:
			return 'Left';
		  case 38:
			return 'Up';
		  case 39:
			return 'Right';
		  case 40:
			return 'Down';
		  case 48:
		  case 49:
		  case 50:
		  case 51:
		  case 52:
		  case 53:
		  case 54:
		  case 55:
		  case 56:
		  case 57:
		  case 65:
		  case 66:
		  case 67:
		  case 68:
		  case 69:
		  case 70:
		  case 71:
		  case 72:
		  case 73:
		  case 74:
		  case 75:
		  case 76:
		  case 77:
		  case 78:
		  case 79:
		  case 80:
		  case 81:
		  case 82:
		  case 83:
		  case 84:
		  case 85:
		  case 86:
		  case 87:
		  case 88:
		  case 89:
		  case 90:
			c = String.fromCharCode(kc);
			if (e.shiftKey) {
			  return c;
			} else {
			  return c.toLowerCase();
			}
			break;
		  default:
			return null;
		}
	  })();
	  if (key) {
		if (e.altKey) key = 'alt+' + key;
		if (e.ctrlKey) key = 'ctrl+' + key;
	  }
	  return key;
	},
	img: function(thread, all) {
	  var root, thumb;
	  if (all) {
		return $("#imageExpand").click();
	  } else {
		root = $('td.replyhl', thread) || thread;
		thumb = $('img[md5]', root);
		return imgExpand.toggle(thumb.parentNode);
	  }
	},
	qr: function(thread, quote) {
	  if (quote) {
		return qr.quote.call($('.quotejs + a', $('.replyhl', thread) || thread));
	  } else {
		if (!qr.el) qr.dialog('', thread != null ? thread.firstChild.id : void 0);
		return $('textarea', qr.el).focus();
	  }
	},
	open: function(thread, tab) {
	  var id, open, url;
	  id = thread.firstChild.id;
	  url = "http://boards.4chan.org/" + g.BOARD + "/res/" + id;
	  if (tab) {
		open = GM_openInTab || window.open;
		return open(url, "_blank");
	  } else {
		return location.href = url;
	  }
	},
	hl: {
	  next: function(thread) {
		var next, rect, replies, reply, td, top, _i, _len;
		if (td = $('td.replyhl', thread)) {
		  td.className = 'reply';
		  rect = td.getBoundingClientRect();
		  if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
			next = $.x('following::td[@class="reply"]', td);
			if ($.x('ancestor::div[@class="thread"]', next) !== thread) return;
			rect = next.getBoundingClientRect();
			if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
			  next.className = 'replyhl';
			}
			return;
		  }
		}
		replies = $$('td.reply', thread);
		for (_i = 0, _len = replies.length; _i < _len; _i++) {
		  reply = replies[_i];
		  top = reply.getBoundingClientRect().top;
		  if (top > 0) {
			reply.className = 'replyhl';
			return;
		  }
		}
	  },
	  prev: function(thread) {
		var bot, height, prev, rect, replies, reply, td, _i, _len;
		if (td = $('td.replyhl', thread)) {
		  td.className = 'reply';
		  rect = td.getBoundingClientRect();
		  if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
			prev = $.x('preceding::td[@class="reply"][1]', td);
			rect = prev.getBoundingClientRect();
			if (rect.top > 0 && rect.bottom < d.body.clientHeight) {
			  prev.className = 'replyhl';
			}
			return;
		  }
		}
		replies = $$('td.reply', thread);
		replies.reverse();
		height = d.body.clientHeight;
		for (_i = 0, _len = replies.length; _i < _len; _i++) {
		  reply = replies[_i];
		  bot = reply.getBoundingClientRect().bottom;
		  if (bot < height) {
			reply.className = 'replyhl';
			return;
		  }
		}
	  }
	}
  };

  nav = {
	init: function() {
	  var next, prev, span;
	  span = $.el('span', {
		id: 'navlinks'
	  });
	  prev = $.el('a', {
		textContent: String.fromCharCode(9650),	//black up triangle		'▲'
		href: 'javascript:;'
	  });
	  next = $.el('a', {
		textContent: String.fromCharCode(9660),	//black down triangle	'▼',
		href: 'javascript:;'
	  });
	  $.on(prev, 'click', nav.prev);
	  $.on(next, 'click', nav.next);
	  $.add(span, prev, $.tn(' '), next);
	  return $.add(d.body, span);
	},
	prev: function() {
	  if (g.REPLY) {
		return window.scrollTo(0, 0);
	  } else {
		return nav.scroll(-1);
	  }
	},
	next: function() {
	  if (g.REPLY) {
		return window.scrollTo(0, d.body.scrollHeight);
	  } else {
		return nav.scroll(+1);
	  }
	},
	top: function() {
	  return window.scroll(0,200);;
	},
	threads: [],
	getThread: function(full) {
	  var bottom, i, rect, thread, _len, _ref;
	  nav.threads = $$('div.thread:not([hidden])');
	  _ref = nav.threads;
	  for (i = 0, _len = _ref.length; i < _len; i++) {
		thread = _ref[i];
		rect = thread.getBoundingClientRect();
		bottom = rect.bottom;
		if (bottom > 0) {
		  if (full) return [thread, i, rect];
		  return thread;
		}
	  }
	  return null;
	},
	scroll: function(delta) {
	  var i, rect, thread, top, _ref, _ref2;
	  _ref = nav.getThread(true), thread = _ref[0], i = _ref[1], rect = _ref[2];
	  top = rect.top;
	  if (!((delta === -1 && Math.ceil(top) < 0) || (delta === +1 && top > 1))) {
		i += delta;
	  }
	  top = (_ref2 = nav.threads[i]) != null ? _ref2.getBoundingClientRect().top : void 0;
	  return window.scrollBy(0, top);
	}
  };

  options = {
	init: function() {
	  var a, home;
	  home = $('#navtopr a');
	  a = $.el('a', {
		textContent: '4chan W',
		href: 'javascript:;'
	  });
	  $.on(a, 'click', options.dialog);
	  $.replace(home, a);
	  home = $('#navbotr a');
	  a = $.el('a', {
		textContent: '4chan W',
		href: 'javascript:;'
	  });
	  $.on(a, 'click', options.dialog);
	  $.replace(home, a);
	  if (!$.get('firstrun')) {
		$.set('firstrun', true);
		return options.dialog();
	  }
	},
	dialog: function() {
	  var arr, back, checked, description, dialog, favicon, hiddenNum, hiddenThreads, indicator, indicators, input, key, li, obj, overlay, ta, time, ul, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3, _ref4;
	  dialog = ui.dialog('options', '', '\
<div id=optionsbar>\
  <div id=credits>\
	<a target=_blank href=http://userscripts.org/scripts/show/125646/>4chan W</a> | ' + VERSION + '\
	| <a target=_blank href=http://userscripts.org/scripts/discuss/125646>Issues</a>\
  </div>\
  <div>\
	<label for=main_tab>Main</label>\
	| <label for=filter_tab>Filter</label>\
	| <label for=flavors_tab>Sauce</label>\
	| <label for=rice_tab>Formatting</label>\
	| <label for=keybinds_tab>Keybinds</label>\
  </div>\
</div>\
<hr>\
<div id=content>\
  <input type=radio name=tab hidden id=main_tab checked>\
  <div></div>\
  <input type=radio name=tab hidden id=flavors_tab>\
  <div>\
	<div class=error><code>Sauce</code> is disabled.</div>\
	<textarea name=flavors id=flavors></textarea>\
  </div>\
  <input type=radio name=tab hidden id=filter_tab>\
  <div>\
	<div class=error><code>Filter</code> is disabled.</div>\
	Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\
	For example, <code>/weeaboo/i</code> will filter posts containing \'weeaboo\' case-insensitive.\
	<p>Name:<br><textarea name=name></textarea></p>\
	<p>Tripcode:<br><textarea name=tripcode></textarea></p>\
	<p>E-mail:<br><textarea name=email></textarea></p>\
	<p>Subject:<br><textarea name=subject></textarea></p>\
	<p>Comment:<br><textarea name=comment></textarea></p>\
	<p>Filename:<br><textarea name=filename></textarea></p>\
	<p>Filesize:<br><textarea name=filesize></textarea></p>\
	<p>Image MD5:<br><textarea name=md5></textarea></p>\
  </div>\
  <input type=radio name=tab hidden id=rice_tab>\
  <div>\
	<div class=error><code>Quote Backlinks</code> are disabled.</div>\
	<ul>\
	  Backlink formatting\
	  <li><input type=text name=backlink> : <span id=backlinkPreview></span></li>\
	</ul>\
	<div class=error><code>Time Formatting</code> is disabled.</div>\
	<ul>\
	  Time formatting\
	  <li><input type=text name=time> : <span id=timePreview></span></li>\
	  <li>Supported <a href=http://en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</li>\
	  <li>Day: %a, %A, %d, %e</li>\
	  <li>Month: %m, %b, %B</li>\
	  <li>Year: %y</li>\
	  <li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>\
	  <li>Minutes: %M</li>\
	</ul>\
	<!--<div class=error><code>Unread Count</code> is disabled.</div>\
	Unread favicons<br>\
	<select name=favicon>\
	  <option value=ferongr>ferongr</option>\
	  <option value=xat->xat-</option>\
	  <option value=Mayhem>Mayhem</option>\
	  <option value=Original>Original</option>\
	  <option value=None>None</option>\
	</select>-->\
	<span></span>\
  </div>\
  <input type=radio name=tab hidden id=keybinds_tab>\
  <div>\
	<div class=error><code>Keybinds</code> are disabled.</div>\
	<table><tbody>\
	  <tr><th>Actions</th><th>Keybinds</th></tr>\
	  <tr><td>Open Options</td><td><input name=openOptions></td></tr>\
	  <tr><td>Close Options or QR</td><td><input name=close></td></tr>\
	  <tr><td>Quick spoiler</td><td><input name=spoiler></td></tr>\
	  <tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr>\
	  <tr><td>Open QR without post number inserted</td><td><input name=openEmptyQR></td></tr>\
	  <tr><td>Submit post</td><td><input name=submit></td></tr>\
	  <tr><td>Select next reply</td><td><input name=nextReply ></td></tr>\
	  <tr><td>Select previous reply</td><td><input name=previousReply></td></tr>\
	  <tr><td>See next thread</td><td><input name=nextThread></td></tr>\
	  <tr><td>See previous thread</td><td><input name=previousThread></td></tr>\
	  <tr><td>Jump to the next page</td><td><input name=nextPage></td></tr>\
	  <tr><td>Jump to the previous page</td><td><input name=previousPage></td></tr>\
	  <tr><td>Jump to page 0</td><td><input name=zero></td></tr>\
	  <tr><td>Open thread in current tab</td><td><input name=openThread></td></tr>\
	  <tr><td>Open thread in new tab</td><td><input name=openThreadTab></td></tr>\
	  <tr><td>Expand thread</td><td><input name=expandThread></td></tr>\
	  <tr><td>Watch thread</td><td><input name=watch></td></tr>\
	  <tr><td>Hide thread</td><td><input name=hide></td></tr>\
	  <tr><td>Expand selected image</td><td><input name=expandImages></td></tr>\
	  <tr><td>Expand all images</td><td><input name=expandAllImages></td></tr>\
	  <tr><td>Update now</td><td><input name=update></td></tr>\
	  <tr><td>Reset the unread count to 0</td><td><input name=unreadCountTo0></td></tr>\
	</tbody></table>\
  </div>\
</div>');
	  _ref = config.main;
	  for (key in _ref) {
		obj = _ref[key];
		ul = $.el('ul', {
		  textContent: key
		});
		for (key in obj) {
		  arr = obj[key];
		  checked = conf[key] ? 'checked' : '';
		  description = arr[1];
		  li = $.el('li', {
			innerHTML: "<label><input type=checkbox name='" + key + "' " + checked + ">" + key + "</label><span class=description>: " + description + "</span>"
		  });
		  $.on($('input', li), 'click', $.cb.checked);
		  $.add(ul, li);
		}
		$.add($('#main_tab + div', dialog), ul);
	  }
	  hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
	  hiddenNum = Object.keys(g.hiddenReplies).length + Object.keys(hiddenThreads).length;
	  li = $.el('li', {
		innerHTML: "<button>hidden: " + hiddenNum + "</button> <span class=description>: Forget all hidden posts. Useful if you accidentally hide a post and have 'Show Stubs' disabled."
	  });
	  $.on($('button', li), 'click', options.clearHidden);
	  $.add($('ul:nth-child(2)', dialog), li);
	  _ref2 = $$('textarea', dialog);
	  for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
		ta = _ref2[_i];
		ta.textContent = conf[ta.name];
		$.on(ta, 'change', $.cb.value);
	  }
	  (back = $('[name=backlink]', dialog)).value = conf['backlink'];
	  (time = $('[name=time]', dialog)).value = conf['time'];
	  $.on(back, 'keyup', $.cb.value);
	  $.on(back, 'keyup', options.backlink);
	  $.on(time, 'keyup', $.cb.value);
	  $.on(time, 'keyup', options.time);
	  /*
	  favicon = $('select', dialog);
	  favicon.value = conf['favicon'];
	  $.on(favicon, 'change', $.cb.value);
	  $.on(favicon, 'change', options.favicon);
	  */
	  _ref3 = $$('#keybinds_tab + div input', dialog);
	  for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
		input = _ref3[_j];
		input.type = 'text';
		input.value = conf[input.name];
		$.on(input, 'keydown', options.keybind);
	  }
	  indicators = {};
	  _ref4 = $$('.error', dialog);
	  for (_k = 0, _len3 = _ref4.length; _k < _len3; _k++) {
		indicator = _ref4[_k];
		key = indicator.firstChild.textContent;
		indicator.hidden = conf[key];
		indicators[key] = indicator;
		$.on($("[name='" + key + "']", dialog), 'click', function() {
		  return indicators[this.name].hidden = this.checked;
		});
	  }
	  overlay = $.el('div', {
		id: 'overlay'
	  });
	  $.on(overlay, 'click', function() {
		return $.rm(overlay);
	  });
	  $.on(dialog, 'click', function(e) {
		return e.stopPropagation();
	  });
	  $.add(overlay, dialog);
	  $.add(d.body, overlay);
	  options.backlink.call(back);
	  /*
	  options.time.call(time);
	  return options.favicon.call(favicon);
	  */
	  return options.time.call(time);
	},
	clearHidden: function() {
	  $["delete"]("hiddenReplies/" + g.BOARD + "/");
	  $["delete"]("hiddenThreads/" + g.BOARD + "/");
	  this.textContent = "hidden: 0";
	  return g.hiddenReplies = {};
	},
	keybind: function(e) {
	  if (e.keyCode === 9) return;
	  e.preventDefault();
	  e.stopPropagation();
	  if ((key = keybinds.keyCode(e)) == null) return;
	  this.value = key;
	  return $.cb.value.call(this);
	},
	time: function() {
	  Time.foo();
	  Time.date = new Date();
	  return $.id('timePreview').textContent = Time.funk(Time);
	},
	backlink: function() {
	  return $.id('backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789');
	},
	/*
	favicon: function() {
	  Favicon["switch"]();
	  if (g.REPLY && conf['Unread Count']) Favicon.update();
	  return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">";
	}
	*/
  };

  cooldown = {
	init: function() {
	  var match, time, _;
	  if (match = location.search.match(/cooldown=(\d+)/)) {
		_ = match[0], time = match[1];
		if ($.get(g.BOARD + '/cooldown', 0) < time) {
		  $.set(g.BOARD + '/cooldown', time);
		}
	  }
	  if (Date.now() < $.get(g.BOARD + '/cooldown', 0)) cooldown.start();
	  $.on(window, 'storage', function(e) {
		if (e.key === ("" + NAMESPACE + g.BOARD + "/cooldown")) {
		  return cooldown.start();
		}
	  });
	  if (g.REPLY) return $('.postarea form').action += '?cooldown';
	},
	start: function() {
	  var submit, _i, _len, _ref, _ref2;
	  cooldown.duration = Math.ceil(($.get(g.BOARD + '/cooldown', 0) - Date.now()) / 1000);
	  if (!((60 >= (_ref = cooldown.duration) && _ref > 0))) return;
	  _ref2 = $$('#com_submit');
	  for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
		submit = _ref2[_i];
		submit.value = cooldown.duration;
		submit.disabled = true;
	  }
	  return setTimeout(cooldown.cb, 1000);
	},
	cb: function() {
	  var submit, submits, _i, _j, _len, _len2, _results;
	  submits = $$('#com_submit');
	  if (--cooldown.duration) {
		setTimeout(cooldown.cb, 1000);
		_results = [];
		for (_i = 0, _len = submits.length; _i < _len; _i++) {
		  submit = submits[_i];
		  _results.push(submit.value = cooldown.duration);
		}
		return _results;
	  } else {
		for (_j = 0, _len2 = submits.length; _j < _len2; _j++) {
		  submit = submits[_j];
		  submit.disabled = false;
		  submit.value = 'Submit';
		}
		return qr.autoPost();
	  }
	}
  };

  qr = {
	init: function() {
	  var iframe;
	  g.callbacks.push(qr.node);
	  $.on($('#recaptcha_challenge_field_holder'), 'DOMNodeInserted', qr.captchaNode);
	  qr.captchaTime = Date.now();
	  qr.spoiler = $('.postarea label') ? '<label> [<input type=checkbox name=spoiler>Spoiler Image?]</label>' : '';
	  qr.acceptFiles = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) {
		switch (type) {
		  case 'JPG':
			return 'image/JPEG';
		  case 'PDF':
			return 'application/' + type;
		  default:
			return 'image/' + type;
		}
	  });
	  iframe = $.el('iframe', {
		name: 'iframe',
		hidden: true
	  });
	  $.add(d.body, iframe);
	  return $('#recaptcha_response_field').id = '';
	},
	attach: function() {
	  var fileDiv;
	  fileDiv = $.el('div', {
		innerHTML: "<input type=file name=upfile accept='" + qr.acceptFiles + "'><a>X</a>"
	  });
	  $.on(fileDiv.firstChild, 'change', qr.validateFileSize);
	  $.on(fileDiv.lastChild, 'click', (function() {
		return $.rm(this.parentNode);
	  }));
	  return $.add($('#files', qr.el), fileDiv);
	},
	attachNext: function() {
	  var file, fileDiv, oldFile;
	  fileDiv = $.rm($('#files div', qr.el));
	  file = fileDiv.firstChild;
	  oldFile = $('#qr_form input[type=file]', qr.el);
	  return $.replace(oldFile, file);
	},
	autoPost: function() {
	  if (qr.el && $('#auto', qr.el).checked) {
		return qr.submit.call($('form', qr.el));
	  }
	},
	captchaNode: function(e) {
	  if (!qr.el) return;
	  val = e.target.value;
	  $('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val;
	  qr.challenge = val;
	  return qr.captchaTime = Date.now();
	},
	captchaKeydown: function(e) {
	  var captchas;
	  if (!(e.keyCode === 13 && this.value)) return;
	  if (cooldown.duration) $('#auto', qr.el).checked = true;
	  captchas = $.get('captchas', []);
	  captchas.push({
		challenge: qr.challenge,
		response: this.value,
		time: qr.captchaTime
	  });
	  $.set('captchas', captchas);
	  $('#captchas', qr.el).textContent = captchas.length + ' captchas';
	  Recaptcha.reload();
	  this.value = '';
	  if (!$('textarea', qr.el).value && !$('input[type=file]', qr.el).files.length) {
		return e.preventDefault();
	  }
	},
	close: function() {
	  $.rm(qr.el);
	  return qr.el = null;
	},
	dialog: function(link) {
	  var THREAD_ID, c, html, m, submitDisabled, submitValue;
	  submitValue = $('#com_submit').value;
	  submitDisabled = $('#com_submit').disabled ? 'disabled' : '';
	  THREAD_ID = g.THREAD_ID || $.x('ancestor::div[@class="thread"]/div', link).id;
	  qr.challenge = $('#recaptcha_challenge_field').value;
	  html = "      <a id=close title=Close class='postblock buttons' style='cursor: pointer; margin-top: 1px;'>X</a>      <input type=checkbox id=autohide title=autohide>      <div class=move>        <input class=inputtext type=text name=name placeholder=Name form=qr_form>        Quick Reply      </div>      <div class=autohide>        <form name=post action=http://sys.4chan.org/" + g.BOARD + "/post method=POST enctype=multipart/form-data target=iframe id=qr_form>          <input type=hidden name=resto value=" + THREAD_ID + ">          <input type=hidden name=mode value=regist>          <input type=hidden name=recaptcha_challenge_field id=recaptcha_challenge_field>          <input type=hidden name=recaptcha_response_field id=recaptcha_response_field>          <div><input class=inputtext type=text name=email placeholder=E-mail>" + qr.spoiler + "</div>          <div><input class=inputtext type=text name=sub placeholder=Subject><input type=submit value=" + submitValue + " id=com_submit " + submitDisabled + "><label><input type=checkbox id=auto>auto</label></div>          <div><textarea class=inputtext name=com placeholder=Comment></textarea></div>          <div><img src=http://www.google.com/recaptcha/api/image?c=" + qr.challenge + "></div>          <div><input class=inputtext type=text autocomplete=off placeholder=Verification id=dummy><span id=captchas>" + ($.get('captchas', []).length) + " captchas</span></div>          <div><input type=file name=upfile accept='" + qr.acceptFiles + "'></div>        </form>        <div id=files></div>        <div><input class=inputtext type=password name=pwd placeholder=Password form=qr_form maxlength=8><a id=attach>attach another file</a></div>      </div>      <a id=error class=error></a>      ";
	  qr.el = ui.dialog('qr', 'top: 0; right: 0;', html);
	  c = d.cookie;
	  $('input[name=name]', qr.el).value = (m = c.match(/4chan_name=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
	  $('input[name=email]', qr.el).value = (m = c.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
	  $('input[name=pwd]', qr.el).value = (m = c.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value;
	  $.on($('input[name=name]', qr.el), 'mousedown', function(e) {
		return e.stopPropagation();
	  });
	  $.on($('input[name=upfile]', qr.el), 'change', qr.validateFileSize);
	  $.on($('#close', qr.el), 'click', qr.close);
	  $.on($('form', qr.el), 'submit', qr.submit);
	  $.on($('#attach', qr.el), 'click', qr.attach);
	  $.on($('img', qr.el), 'click', Recaptcha.reload);
	  $.on($('#dummy', qr.el), 'keydown', Recaptcha.listener);
	  $.on($('#dummy', qr.el), 'keydown', qr.captchaKeydown);
	  return $.add(d.body, qr.el);
	},
	message: function(data) {
	  var duration, fileCount, tc;
	  $('iframe[name=iframe]').src = 'about:blank';
	  fileCount = $('#files', qr.el).childElementCount;
	  tc = data.textContent;
	  if (!/successful!|uploaded!$/.test(tc)) {
		if (tc === void 0) {
		  data.textContent = "Connection error with sys.4chan.org.";
		}
		$.extend($('#error', qr.el), data);
		$('#recaptcha_response_field', qr.el).value = '';
		$('#autohide', qr.el).checked = false;
		if (tc === 'You seem to have mistyped the verification.') {
		  setTimeout(qr.autoPost, 1000);
		} else if (tc === 'Error: Duplicate file entry detected.' && fileCount) {
		  $('textarea', qr.el).value += '\n' + tc + ' ' + data.href;
		  qr.attachNext();
		  setTimeout(qr.autoPost, 1000);
		}
		return;
	  }
	  if (qr.el) {
		if (g.REPLY && (conf['Persistent QR'] || fileCount)) {
		  qr.refresh();
		  if (fileCount) qr.attachNext();
		} else {
		  qr.close();
		}
	  }
	  if (conf['Cooldown']) {
		duration = qr.sage ? 60 : 30;
		$.set(g.BOARD + '/cooldown', Date.now() + duration * 1000);
		return cooldown.start();
	  }
	},
	node: function(root) {
	  var quote;
	  quote = $('a.quotejs:not(:first-child)', root);
	  return $.on(quote, 'click', qr.quote);
	},
	postInvalid: function() {
	  var captcha, captchas, content, cutoff, dummy, response;
	  content = $('textarea', qr.el).value || $('input[type=file]', qr.el).files.length;
	  if (!content) return 'Error: No text entered.';
	  /*
		  captchas expire after 30 minutes, see window.RecaptchaState.timeout.
		  cutoff 5 minutes before then, b/c posting takes time.
	  */
	  cutoff = Date.now() - 25 * MINUTE;
	  captchas = $.get('captchas', []);
	  while (captcha = captchas.shift()) {
		if (captcha.time > cutoff) break;
	  }
	  $.set('captchas', captchas);
	  $('#captchas', qr.el).textContent = captchas.length + ' captchas';
	  if (!captcha) {
		dummy = $('#dummy', qr.el);
		if (!(response = dummy.value)) {
		  return 'You forgot to type in the verification';
		}
		captcha = {
		  challenge: qr.challenge,
		  response: response
		};
		dummy.value = '';
		Recaptcha.reload();
	  }
	  $('#recaptcha_challenge_field', qr.el).value = captcha.challenge;
	  $('#recaptcha_response_field', qr.el).value = captcha.response;
	  return false;
	},
	quote: function(e) {
	  var caretPos, id, s, selection, selectionID, ta, text, _ref;
	  if (e) e.preventDefault();
	  if (qr.el) {
		$('#autohide', qr.el).checked = false;
	  } else {
		qr.dialog(this);
	  }
	  
	  var oldtext = '';
	  if (conf['Relocate Text']) {
		  var textarea = document.getElementsByTagName("textarea")[0];
		  if (textarea.value != '') {
			  oldtext = textarea.value + '\n';
			  textarea.value = '';
		  }
	  }
	  
	  id = this.textContent;
	  text = ">>" + id + "\n";
	  selection = window.getSelection();
	  if (s = selection.toString()) {
		selectionID = (_ref = $.x('ancestor-or-self::blockquote/preceding-sibling::input', selection.anchorNode)) != null ? _ref.name : void 0;
		if (selectionID === id) {
		  s = s.replace(/\n/g, '\n>');
		  text += ">" + s + "\n";
		}
	  }
	  ta = $('textarea', qr.el);
	  caretPos = ta.selectionStart;
// 	  ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd, ta.value.length);
	  ta.value = ta.value.slice(0, caretPos) + oldtext + text + ta.value.slice(ta.selectionEnd, ta.value.length);
	  ta.focus();
	  return ta.selectionEnd = ta.selectionStart = caretPos + text.length + 1 * (engine === 'presto');
	},
	refresh: function() {
	  var m, newFile, oldFile, _ref;
	  $('[name=sub]', qr.el).value = '';
	  $('[name=email]', qr.el).value = (m = d.cookie.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
	  $('[name=com]', qr.el).value = '';
	  $('[name=recaptcha_response_field]', qr.el).value = '';
	  if (!conf['Remember Spoiler']) {
		if ((_ref = $('[name=spoiler]', qr.el)) != null) _ref.checked = false;
	  }
	  oldFile = $('[type=file]', qr.el);
	  newFile = $.el('input', {
		type: 'file',
		name: 'upfile',
		accept: qr.acceptFiles
	  });
	  return $.replace(oldFile, newFile);
	},
	submit: function(e) {
	  var id, msg, op;
	  if (msg = qr.postInvalid()) {
		if (typeof e.preventDefault === "function") e.preventDefault();
		alert(msg);
		if (msg === 'You forgot to type in the verification.') {
		  $('#dummy', qr.el).focus();
		}
		return;
	  }
	  if (conf['Auto Watch Reply'] && conf['Thread Watcher']) {
		if (g.REPLY && $('img.favicon').src === Favicon.empty) {
		  watcher.watch(null, g.THREAD_ID);
		} else {
		  id = $('input[name=resto]', qr.el).value;
		  op = $.id(id);
		  if ($('img.favicon', op).src === Favicon.empty) watcher.watch(op, id);
		}
	  }
	  if (!e) this.submit();
	  $('#error', qr.el).textContent = '';
	  if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true;
	  return qr.sage = /sage/i.test($('input[name=email]', this).value);
	},
	sys: function() {
	  var c, duration, id, noko, recaptcha, sage, search, thread, url, watch, _, _ref, _ref2;
	  if (recaptcha = $('#recaptcha_response_field')) {
		$.on(recaptcha, 'keydown', Recaptcha.listener);
		return;
	  }
	  /*
			http://code.google.com/p/chromium/issues/detail?id=20773
			Let content scripts see other frames (instead of them being undefined)
	  
			To access the parent, we have to break out of the sandbox and evaluate
			in the global context.
	  */
	  $.globalEval(function() {
		var data, node, _ref;
		data = {};
		if (node = (_ref = document.querySelector('td b')) != null ? _ref.firstChild : void 0) {
		  data.textContent = node.textContent;
		  if (node.href) data.href = node.href;
		}
		return parent.postMessage(data, '*');
	  });
	  c = (_ref = $('b')) != null ? _ref.lastChild : void 0;
	  if (!(c && c.nodeType === 8)) return;
	  _ref2 = c.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref2[0], thread = _ref2[1], id = _ref2[2];
	  search = location.search;
	  cooldown = /cooldown/.test(search);
	  noko = /noko/.test(search);
	  sage = /sage/.test(search);
	  watch = /watch/.test(search);
	  url = "http://boards.4chan.org/" + g.BOARD;
	  if (watch && thread === '0') {
		url += "/res/" + id + "?watch";
	  } else if (noko) {
		url += '/res/';
		url += thread === '0' ? id : thread;
	  }
	  if (cooldown) {
		duration = Date.now() + (sage ? 60 : 30) * 1000;
		url += '?cooldown=' + duration;
	  }
	  if (noko) url += '#' + id;
	  return window.location = url;
	},
	validateFileSize: function(e) {
	  var file;
	  if (!(this.files[0].size > $('input[name=MAX_FILE_SIZE]').value)) return;
	  file = $.el('input', {
		type: 'file',
		name: 'upfile',
		accept: qr.acceptFiles
	  });
	  $.on(file, 'change', qr.validateFileSize);
	  $.replace(this, file);
	  $('#error', qr.el).textContent = 'Error: File too large.';
	  return alert('Error: File too large.');
	}
  };

  Recaptcha = {
	init: function() {
	  var el, _i, _len, _ref;
	  _ref = $$('#recaptcha_table a');
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		el = _ref[_i];
		el.tabIndex = 1;
	  }
	  return $.on($('#recaptcha_response_field'), 'keydown', Recaptcha.listener);
	},
	listener: function(e) {
	  if (e.keyCode === 8 && this.value === '') return Recaptcha.reload();
	},
	reload: function() {
	  return window.location = 'javascript:Recaptcha.reload()';
	}
  };

  threading = {
	init: function() {
	  return threading.thread($('body > form').firstChild);
	},
	op: function(node) {
	  var op;
	  op = $.el('div', {
		className: 'op'
	  });
	  $.before(node, op);
	  while (node.nodeName !== 'BLOCKQUOTE') {
		$.add(op, node);
		node = op.nextSibling;
	  }
	  $.add(op, node);
	  op.id = $('input', op).name;
	  return op;
	},
	thread: function(node) {
	  var div;
	  node = threading.op(node);
	  if (g.REPLY) return;
	  div = $.el('div', {
		className: 'thread'
	  });
	  $.before(node, div);
	  while (node.nodeName !== 'HR') {
		$.add(div, node);
		node = div.nextSibling;
	  }
	  node = node.nextElementSibling;
	  if (!(node.align || node.nodeName === 'CENTER')) {
		return threading.thread(node);
	  }
	}
  };

  threadHiding = {
	init: function() {
	  var a, hiddenThreads, op, thread, _i, _len, _ref, _results;
	  hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
	  _ref = $$('.thread');
	  _results = [];
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		thread = _ref[_i];
		op = thread.firstChild;
		if (conf['Style Buttons']) {
			a = $.el('a', {
			  textContent: '--',
			  className: 'postblock buttons',
			  href: 'javascript:;'
			});	
		} else {
			a = $.el('a', {
			  textContent: '[ - ]',
			  href: 'javascript:;'
			});	
		}
		$.on(a, 'click', threadHiding.cb.hide);
		$.prepend(op, a);
		if (op.id in hiddenThreads) {
		  _results.push(threadHiding.hideHide(thread));
		} else {
		  _results.push(void 0);
		}
	  }
	  return _results;
	},
	cb: {
	  hide: function() {
		var thread;
		thread = this.parentNode.parentNode;
		return threadHiding.hide(thread);
	  },
	  show: function() {
		var thread;
		thread = this.parentNode.parentNode;
		return threadHiding.show(thread);
	  }
	},
	toggle: function(thread) {
	  if (/\bstub\b/.test(thread.className) || thread.hidden) {
		return threadHiding.show(thread);
	  } else {
		return threadHiding.hide(thread);
	  }
	},
	hide: function(thread) {
	  var hiddenThreads, id;
	  threadHiding.hideHide(thread);
	  id = thread.firstChild.id;
	  hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
	  hiddenThreads[id] = Date.now();
	  return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
	},
	hideHide: function(thread) {
	  var a, div, name, num, span, text, trip, _ref;
	  if (conf['Show Stubs']) {
		if (/stub/.test(thread.className)) return;
		if (span = $('.omittedposts', thread)) {
		  num = Number(span.textContent.match(/\d+/)[0]);
		} else {
		  num = 0;
		}
		num += $$('table', thread).length;
		text = num === 1 ? "1 reply" : "" + num + " replies";
		name = $('.postername', thread).textContent;
		trip = ((_ref = $('.postername + .postertrip', thread)) != null ? _ref.textContent : void 0) || '';
		
		if (conf['Style Buttons']) {
			a = $.el('a', {
			  innerHTML: "<span class='postblock buttons' style='padding-left: 3px; padding-right: 3px;'>+</span> " + name + trip + " (" + text + ")",
			  href: 'javascript:;'
			});
		} else {
			a = $.el('a', {
	 		  innerHTML: "<span>[ + ]</span> " + name + trip + " (" + text + ")",
			  href: 'javascript:;'
			});
		}
		
		$.on(a, 'click', threadHiding.cb.show);
		div = $.el('div', {
		  className: 'block'
		});
		$.add(div, a);
		$.add(thread, div);
		return $.addClass(thread, 'stub');
	  } else {
		thread.hidden = true;
		return thread.nextSibling.hidden = true;
	  }
	},
	show: function(thread) {
	  var hiddenThreads, id;
	  $.rm($('div.block', thread));
	  $.removeClass(thread, 'stub');
	  thread.hidden = false;
	  thread.nextSibling.hidden = false;
	  id = thread.firstChild.id;
	  hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
	  delete hiddenThreads[id];
	  return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
	}
  };

  updater = {
	init: function() {
	  var checkbox, checked, dialog, html, input, name, title, _i, _len, _ref;
	  if (conf['Scrolling']) {
		if (conf['Scroll BG']) {
		  updater.focus = true;
		} else {
		  $.on(window, 'focus', (function() {
			return updater.focus = true;
		  }));
		  $.on(window, 'blur', (function() {
			return updater.focus = false;
		  }));
		}
	  }
	  html = "<div class=move><span id=count></span> <span id=timer>-" + conf['Interval'] + "</span></div>";
	  checkbox = config.updater.checkbox;
	  for (name in checkbox) {
		title = checkbox[name][1];
		checked = conf[name] ? 'checked' : '';
		html += "<div><label title='" + title + "'>" + name + "<input name='" + name + "' type=checkbox " + checked + "></label></div>";
	  }
	  checked = conf['Auto Update'] ? 'checked' : '';
	  html += "      <div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox " + checked + "></label></div>      <div><label>Interval (s)<input name=Interval value=" + conf['Interval'] + " type=text></label></div>      <div><input value='Update Now' type=button></div>";
	  dialog = ui.dialog('updater', 'bottom: 0; right: 0;', html);
	  updater.count = $('#count', dialog);
	  updater.timer = $('#timer', dialog);
	  updater.br = $('br[clear]');
	  _ref = $$('input', dialog);
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		input = _ref[_i];
		if (input.type === 'checkbox') {
		  $.on(input, 'click', $.cb.checked);
		  $.on(input, 'click', function() {
			return conf[this.name] = this.checked;
		  });
		  if (input.name === 'Verbose') {
			$.on(input, 'click', updater.cb.verbose);
			updater.cb.verbose.call(input);
		  } else if (input.name === 'Auto Update This') {
			$.on(input, 'click', updater.cb.autoUpdate);
			updater.cb.autoUpdate.call(input);
		  }
		} else if (input.name === 'Interval') {
		  $.on(input, 'change', function() {
			return conf['Interval'] = this.value = parseInt(this.value, 10) || conf['Interval'];
		  });
		  $.on(input, 'change', $.cb.value);
		} else if (input.type === 'button') {
		  $.on(input, 'click', updater.update);
		}
	  }
	  $.add(d.body, dialog);
	  updater.retryCoef = 10;
	  return updater.lastModified = 0;
	},
	cb: {
	  verbose: function() {
		if (conf['Verbose']) {
		  updater.count.textContent = '+0';
		  return updater.timer.hidden = false;
		} else {
		  $.extend(updater.count, {
			className: '',
			textContent: 'Thread Updater'
		  });
		  return updater.timer.hidden = true;
		}
	  },
	  autoUpdate: function() {
		if (this.checked) {
		  return updater.timeoutID = setTimeout(updater.timeout, 1000);
		} else {
		  return clearTimeout(updater.timeoutID);
		}
	  },
	  update: function() {
		var body, frag, id, input, newPosts, reply, scroll, _i, _j, _len, _len2, _ref, _ref2, _ref3;
		if (this.status === 404) {
		  updater.timer.textContent = '';
		  updater.count.textContent = 404;
		  updater.count.className = 'error';
		  clearTimeout(updater.timeoutID);
		  _ref = $$('#com_submit');
		  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
			input = _ref[_i];
			input.disabled = true;
			input.value = 404;
		  }
		  d.title = d.title.match(/^.+-/)[0] + ' 404';
		  g.dead = true;
		  Favicon.update();
		  return;
		}
		updater.retryCoef = 10;
		updater.timer.textContent = '-' + conf['Interval'];
		/*
			  Status Code 304: Not modified
			  By sending the `If-Modified-Since` header we get a proper status code, and no response.
			  This saves bandwidth for both the user and the servers, avoid unnecessary computation,
			  and won't load images and scripts when parsing the response.
		*/
		if (this.status === 304) {
		  if (conf['Verbose']) {
			updater.count.textContent = '+0';
			updater.count.className = null;
		  }
		  return;
		}
		updater.lastModified = this.getResponseHeader('Last-Modified');
		body = $.el('body', {
		  innerHTML: this.responseText
		});
		if ($('title', body).textContent === '4chan - Banned') {
		  updater.count.textContent = 'Banned';
		  updater.count.className = 'error';
		  return;
		}
		id = ((_ref2 = $('td[id]', updater.br.previousElementSibling)) != null ? _ref2.id : void 0) || 0;
		frag = d.createDocumentFragment();
		_ref3 = $$('.reply', body).reverse();
		for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
		  reply = _ref3[_j];
		  if (reply.id <= id) break;
		  $.prepend(frag, reply.parentNode.parentNode.parentNode);
		}
		newPosts = frag.childNodes.length;
		scroll = conf['Scrolling'] && updater.focus && newPosts && (d.body.scrollHeight - d.body.clientHeight - window.scrollY < 20);
		if (conf['Verbose']) {
		  updater.count.textContent = '+' + newPosts;
		  if (newPosts === 0) {
			updater.count.className = null;
		  } else {
			updater.count.className = 'new';
		  }
		}
		$.before(updater.br, frag);
		if (scroll) return scrollTo(0, d.body.scrollHeight);
	  }
	},
	timeout: function() {
	  var n;
	  updater.timeoutID = setTimeout(updater.timeout, 1000);
	  n = 1 + Number(updater.timer.textContent);
	  if (n === 0) {
		return updater.update();
	  } else if (n === updater.retryCoef) {
		updater.retryCoef += 10 * (updater.retryCoef < 120);
		return updater.retry();
	  } else {
		return updater.timer.textContent = n;
	  }
	},
	retry: function() {
	  updater.count.textContent = 'Retry';
	  updater.count.className = '';
	  return updater.update();
	},
	update: function() {
	  var url, _ref;
	  updater.timer.textContent = 0;
	  if ((_ref = updater.request) != null) _ref.abort();
	  url = location.pathname + '?' + Date.now();
	  return updater.request = $.ajax(url, updater.cb.update, {
		headers: {
		  'If-Modified-Since': updater.lastModified
		}
	  });
	}
  };

  watcher = {
	init: function() {
	  var favicon, html, input, inputs, _i, _len;
	  html = '<div class=move>Thread Watcher</div>';
	  watcher.dialog = ui.dialog('watcher', 'top: 50px; left: 0px;', html);
	  $.add(d.body, watcher.dialog);
	  inputs = $$('.op input');
	  for (_i = 0, _len = inputs.length; _i < _len; _i++) {
		input = inputs[_i];
		favicon = $.el('img', {
		  className: 'favicon'
		});
		$.on(favicon, 'click', watcher.cb.toggle);
		$.before(input, favicon);
	  }
	  watcher.refresh();
	  if (conf['Auto Watch']) {
		if (!g.REPLY) {
		  $('.postarea form').action += '?watch';
		} else if (/watch/.test(location.search) && $('img.favicon').src === Favicon.empty) {
		  watcher.watch(null, g.THREAD_ID);
		}
	  }
	  return $.on(window, 'storage', function(e) {
		if (e.key === ("" + NAMESPACE + "watched")) return watcher.refresh();
	  });
	},
	refresh: function() {
	  var board, div, favicon, frag, id, link, props, watched, watchedBoard, x, _i, _j, _len, _len2, _ref, _ref2, _ref3, _results;
	  watched = $.get('watched', {});
	  frag = d.createDocumentFragment();
	  for (board in watched) {
		_ref = watched[board];
		for (id in _ref) {
		  props = _ref[id];
		  x = $.el('a', {
			textContent: 'X',
			href: 'javascript:;'
		  });
		  $.on(x, 'click', watcher.cb.x);
		  link = $.el('a', props);
		  link.title = link.textContent;
		  div = $.el('div');
		  $.add(div, x, $.tn(' '), link);
		  $.add(frag, div);
		}
	  }
	  _ref2 = $$('div:not(.move)', watcher.dialog);
	  for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
		div = _ref2[_i];
		$.rm(div);
	  }
	  $.add(watcher.dialog, frag);
	  watchedBoard = watched[g.BOARD] || {};
	  _ref3 = $$('img.favicon');
	  _results = [];
	  for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) {
		favicon = _ref3[_j];
		id = favicon.nextSibling.name;
		if (id in watchedBoard) {
		  _results.push(favicon.src = Favicon["default"]);
		} else {
		  _results.push(favicon.src = Favicon.empty);
		}
	  }
	  return _results;
	},
	cb: {
	  toggle: function() {
		return watcher.toggle(this.parentNode);
	  },
	  x: function() {
		var board, id, _, _ref;
		_ref = this.nextElementSibling.getAttribute('href').slice(1).split('/'), board = _ref[0], _ = _ref[1], id = _ref[2];
		return watcher.unwatch(board, id);
	  }
	},
	toggle: function(thread) {
	  var favicon, id;
	  favicon = $('img.favicon', thread);
	  id = favicon.nextSibling.name;
	  if (favicon.src === Favicon.empty) {
		return watcher.watch(thread, id);
	  } else {
		return watcher.unwatch(g.BOARD, id);
	  }
	},
	unwatch: function(board, id) {
	  var watched;
	  watched = $.get('watched', {});
	  delete watched[board][id];
	  $.set('watched', watched);
	  return watcher.refresh();
	},
	watch: function(thread, id) {
	  var props, text, watched, _name;
	  text = getTitle(thread);
	  props = {
		href: "/" + g.BOARD + "/res/" + id,
		textContent: text
	  };
	  watched = $.get('watched', {});
	  watched[_name = g.BOARD] || (watched[_name] = {});
	  watched[g.BOARD][id] = props;
	  $.set('watched', watched);
	  return watcher.refresh();
	}
  };

  anonymize = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var name, trip;
		name = $('.commentpostername, .postername', root);
		name.textContent = 'Anonymous';
		if (trip = $('.postertrip', root)) {
		  if (trip.parentNode.nodeName === 'A') {
			return $.rm(trip.parentNode);
		  } else {
			return $.rm(trip);
		  }
		}
	  });
	}
  };

  sauce = {
	init: function() {
	  if (!(sauce.prefixes = conf['flavors'].match(/^[^#].+$/gm))) return;
	  sauce.names = sauce.prefixes.map(function(prefix) {
		return prefix.match(/(\w+)\./)[1];
	  });
	  return g.callbacks.push(function(root) {
		var i, link, prefix, span, suffix, _len, _ref, _results;
		if (root.className === 'inline' || !(span = $('.filesize', root))) return;
		suffix = $('a', span).href;
		_ref = sauce.prefixes;
		_results = [];
		for (i = 0, _len = _ref.length; i < _len; i++) {
		  prefix = _ref[i];
		  link = $.el('a', {
			textContent: sauce.names[i],
			href: prefix + suffix,
			target: '_blank'
		  });
		  _results.push($.add(span, $.tn(' '), link));
		}
		return _results;
	  });
	}
  };

  revealSpoilers = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var board, img, imgID, _, _ref;
		if (!(img = $('img[alt^=Spoiler]', root)) || root.className === 'inline') {
		  return;
		}
		img.removeAttribute('height');
		img.removeAttribute('width');
		_ref = img.parentNode.href.match(/(\w+)\/src\/(\d+)/), _ = _ref[0], board = _ref[1], imgID = _ref[2];
		return img.src = "http://0.thumbs.4chan.org/" + board + "/thumb/" + imgID + "s.jpg";
	  });
	}
  };

  Time = {
	init: function() {
	  var chanOffset;
	  Time.foo();
	  chanOffset = 5 - new Date().getTimezoneOffset() / 60;
	  if ($.isDST()) chanOffset--;
	  this.parse = Date.parse('10/11/11(Tue)18:53' === 1318351980000) ? function(node) {
		return new Date(Date.parse(node.textContent) + chanOffset * HOUR);
	  } : function(node) {
		var day, hour, min, month, year, _, _ref;
		_ref = node.textContent.match(/(\d+)\/(\d+)\/(\d+)\(\w+\)(\d+):(\d+)/), _ = _ref[0], month = _ref[1], day = _ref[2], year = _ref[3], hour = _ref[4], min = _ref[5];
		year = "20" + year;
		month -= 1;
		hour = chanOffset + Number(hour);
		return new Date(year, month, day, hour, min);
	  };
	  return g.callbacks.push(Time.node);
	},
	node: function(root) {
	  var node, posttime, time;
	  if (root.className === 'inline') return;
	  node = (posttime = $('.posttime', root)) ? posttime : $('span[id]', root).previousSibling;
	  Time.date = Time.parse(node);
	  time = $.el('time', {
		textContent: ' ' + Time.funk(Time) + ' '
	  });
	  return $.replace(node, time);
	},
	foo: function() {
	  var code;
	  code = conf['time'].replace(/%([A-Za-z])/g, function(s, c) {
		if (c in Time.formatters) {
		  return "' + Time.formatters." + c + "() + '";
		} else {
		  return s;
		}
	  });
	  return Time.funk = Function('Time', "return '" + code + "'");
	},
	day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	zeroPad: function(n) {
	  if (n < 10) {
		return '0' + n;
	  } else {
		return n;
	  }
	},
	formatters: {
	  a: function() {
		return Time.day[Time.date.getDay()].slice(0, 3);
	  },
	  A: function() {
		return Time.day[Time.date.getDay()];
	  },
	  b: function() {
		return Time.month[Time.date.getMonth()].slice(0, 3);
	  },
	  B: function() {
		return Time.month[Time.date.getMonth()];
	  },
	  d: function() {
		return Time.zeroPad(Time.date.getDate());
	  },
	  e: function() {
		return Time.date.getDate();
	  },
	  H: function() {
		return Time.zeroPad(Time.date.getHours());
	  },
	  I: function() {
		return Time.zeroPad(Time.date.getHours() % 12 || 12);
	  },
	  k: function() {
		return Time.date.getHours();
	  },
	  l: function() {
		return Time.date.getHours() % 12 || 12;
	  },
	  m: function() {
		return Time.zeroPad(Time.date.getMonth() + 1);
	  },
	  M: function() {
		return Time.zeroPad(Time.date.getMinutes());
	  },
	  p: function() {
		if (Time.date.getHours() < 12) {
		  return 'AM';
		} else {
		  return 'PM';
		}
	  },
	  P: function() {
		if (Time.date.getHours() < 12) {
		  return 'am';
		} else {
		  return 'pm';
		}
	  },
	  y: function() {
		return Time.date.getFullYear() - 2000;
	  }
	}
  };

  getTitle = function(thread) {
	var el, span;
	el = $('.filetitle', thread);
	if (!el.textContent) {
	  el = $('blockquote', thread);
	  if (!el.textContent) el = $('.postername', thread);
	}
	span = $.el('span', {
	  innerHTML: el.innerHTML.replace(/<br>/g, ' ')
	});
	return "/" + g.BOARD + "/ - " + span.textContent;
  };

  titlePost = {
	init: function() {
	  return d.title = getTitle();
	}
  };

  quoteBacklink = {
	init: function() {
	  var format;
	  format = conf['backlink'].replace(/%id/, "' + id + '");
	  quoteBacklink.funk = Function('id', "return'" + format + "'");
	  return g.callbacks.push(function(root) {
		var a, container, el, id, link, qid, quote, quotes, _i, _len, _ref, _results;
		if (/\binline\b/.test(root.className)) return;
		quotes = {};
		_ref = $$('.quotelink', root);
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (qid = quote.hash.slice(1)) quotes[qid] = quote;
		}
		id = $('input', root).name;
		a = $.el('a', {
		  href: "#" + id,
		  className: root.hidden ? 'filtered backlink' : 'backlink',
		  textContent: quoteBacklink.funk(id)
		});
		_results = [];
		for (qid in quotes) {
		  if (!(el = $.id(qid))) continue;
		  if (el.className === 'op' && !conf['OP Backlinks']) continue;
		  link = a.cloneNode(true);
		  if (conf['Quote Preview']) {
			$.on(link, 'mouseover', quotePreview.mouseover);
			$.on(link, 'mousemove', ui.hover);
			$.on(link, 'mouseout', quotePreview.mouseout);
		  }
		  if (conf['Quote Inline']) $.on(link, 'click', quoteInline.toggle);
		  if (!((container = $('.container', el)) && container.parentNode === el)) {
			container = $.el('span', {
			  className: 'container'
			});
			root = $('.reportbutton', el) || $('span[id]', el);
			$.after(root, container);
		  }
		  _results.push($.add(container, $.tn(' '), link));
		}
		return _results;
	  });
	}
  };

  quoteInline = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var quote, _i, _len, _ref, _results;
		_ref = $$('.quotelink, .backlink', root);
		_results = [];
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (!quote.hash) continue;
		  quote.removeAttribute('onclick');
		  _results.push($.on(quote, 'click', quoteInline.toggle));
		}
		return _results;
	  });
	},
	toggle: function(e) {
	  var id;
	  if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
		return;
	  }
	  e.preventDefault();
	  id = this.hash.slice(1);
	  if (/\binlined\b/.test(this.className)) {
		quoteInline.rm(this, id);
	  } else {
		if ($.x("ancestor::*[@id='" + id + "']", this)) return;
		quoteInline.add(this, id);
	  }
	  return this.classList.toggle('inlined');
	},
	add: function(q, id) {
	  var el, i, inline, pathname, root, threadID;
	  root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q;
	  if (el = $.id(id)) {
		inline = quoteInline.table(id, el.innerHTML);
		if (g.REPLY && conf['Unread Count'] && (i = unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) {
		  unread.replies.splice(i, 1);
		  unread.updateTitle();
		  Favicon.update();
		}
		if (/\bbacklink\b/.test(q.className)) {
		  $.after(q.parentNode, inline);
		  if (conf['Forward Hiding']) {
			$.addClass($.x('ancestor::table', el), 'forwarded');
		  }
		  return;
		}
		return $.after(root, inline);
	  } else {
		inline = $.el('td', {
		  className: 'reply inline',
		  id: "i" + id,
		  innerHTML: "Loading " + id + "..."
		});
		$.after(root, inline);
		pathname = q.pathname;
		threadID = pathname.split('/').pop();
		return $.cache(pathname, (function() {
		  return quoteInline.parse(this, pathname, id, threadID, inline);
		}));
	  }
	},
	rm: function(q, id) {
	  var inlined, table, _i, _len, _ref;
	  table = $.x("following::*[@id='i" + id + "']", q);
	  $.rm(table);
	  if (!conf['Forward Hiding']) return;
	  _ref = $$('.backlink.inlined', table);
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		inlined = _ref[_i];
		$.removeClass($.x('ancestor::table', $.id(inlined.hash.slice(1))), 'forwarded');
	  }
	  if (/\bbacklink\b/.test(q.className)) {
		return $.removeClass($.x('ancestor::table', $.id(id)), 'forwarded');
	  }
	},
	parse: function(req, pathname, id, threadID, inline) {
	  var body, href, html, link, newInline, op, quote, reply, _i, _j, _len, _len2, _ref, _ref2;
	  if (!inline.parentNode) return;
	  if (req.status !== 200) {
		inline.innerHTML = "" + req.status + " " + req.statusText;
		return;
	  }
	  body = $.el('body', {
		innerHTML: req.responseText
	  });
	  if (id === threadID) {
		op = threading.op($('body > form', body).firstChild);
		html = op.innerHTML;
	  } else {
		_ref = $$('td.reply', body);
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  reply = _ref[_i];
		  if (reply.id === id) {
			html = reply.innerHTML;
			break;
		  }
		}
	  }
	  newInline = quoteInline.table(id, html);
	  _ref2 = $$('.quotelink', newInline);
	  for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
		quote = _ref2[_j];
		if ((href = quote.getAttribute('href')) === quote.hash) {
		  quote.pathname = pathname;
		} else if (!g.REPLY && href !== quote.href) {
		  quote.href = "res/" + href;
		}
	  }
	  link = $('.quotejs', newInline);
	  link.href = "" + pathname + "#" + id;
	  link.nextSibling.href = "" + pathname + "#q" + id;
	  $.addClass(newInline, 'crossquote');
	  return $.replace(inline, newInline);
	},
	table: function(id, html) {
	  return $.el('table', {
		className: 'inline',
		id: "i" + id,
		innerHTML: "<tbody><tr><td class=reply>" + html + "</td></tr></tbody>"
	  });
	}
  };

  quotePreview = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var quote, _i, _len, _ref, _results;
		_ref = $$('.quotelink, .backlink', root);
		_results = [];
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (!quote.hash) continue;
		  $.on(quote, 'mouseover', quotePreview.mouseover);
		  $.on(quote, 'mousemove', ui.hover);
		  _results.push($.on(quote, 'mouseout', quotePreview.mouseout));
		}
		return _results;
	  });
	},
	mouseover: function(e) {
	  var el, id, qp, quote, replyID, threadID, _i, _len, _ref, _results;
	  qp = ui.el = $.el('div', {
		id: 'qp',
		className: 'reply dialog'
	  });
	  $.add(d.body, qp);
	  id = this.hash.slice(1);
	  if (el = $.id(id)) {
		qp.innerHTML = el.innerHTML;
		if (conf['Quote Highlighting']) $.addClass(el, 'qphl');
		if (/\bbacklink\b/.test(this.className)) {
		  replyID = $.x('preceding-sibling::input', this.parentNode).name;
		  _ref = $$('.quotelink', qp);
		  _results = [];
		  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
			quote = _ref[_i];
			if (quote.hash.slice(1) === replyID) {
			  _results.push(quote.className = 'forwardlink');
			} else {
			  _results.push(void 0);
			}
		  }
		  return _results;
		}
	  } else {
		qp.innerHTML = "Loading " + id + "...";
		threadID = this.pathname.split('/').pop() || $.x('ancestor::div[@class="thread"]/div', this).id;
		$.cache(this.pathname, (function() {
		  return quotePreview.parse(this, id, threadID);
		}));
		return ui.hover(e);
	  }
	},
	mouseout: function() {
	  var el;
	  if (el = $.id(this.hash.slice(1))) $.removeClass(el, 'qphl');
	  return ui.hoverend();
	},
	parse: function(req, id, threadID) {
	  var body, html, op, qp, reply, _i, _len, _ref;
	  if (!((qp = ui.el) && (qp.innerHTML === ("Loading " + id + "...")))) return;
	  if (req.status !== 200) {
		qp.innerHTML = "" + req.status + " " + req.statusText;
		return;
	  }
	  body = $.el('body', {
		innerHTML: req.responseText
	  });
	  if (id === threadID) {
		op = threading.op($('body > form', body).firstChild);
		html = op.innerHTML;
	  } else {
		_ref = $$('td.reply', body);
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  reply = _ref[_i];
		  if (reply.id === id) {
			html = reply.innerHTML;
			break;
		  }
		}
	  }
	  qp.innerHTML = html;
	  return Time.node(qp);
	}
  };

  quoteOP = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var quote, tid, _i, _len, _ref, _results;
		if (root.className === 'inline') return;
		tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]/div', root).id;
		_ref = $$('.quotelink', root);
		_results = [];
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (quote.hash.slice(1) === tid) {
			_results.push(quote.innerHTML += '&nbsp;(OP)');
		  } else {
			_results.push(void 0);
		  }
		}
		return _results;
	  });
	}
  };

  quoteDR = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var quote, tid, _i, _len, _ref, _results;
		if (root.className === 'inline') return;
		tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]/div', root).id;
		_ref = $$('.quotelink', root);
		_results = [];
		for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		  quote = _ref[_i];
		  if (quote.pathname.indexOf("res/" + tid) === -1 && !quote.pathname.indexOf("/" + g.BOARD + "/res")) {
			_results.push(quote.innerHTML += '&nbsp;(Cross-thread)');
		  } else {
			_results.push(void 0);
		  }
		}
		return _results;
	  });
	}
  };

  reportButton = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var a, span;
		if (!(a = $('.reportbutton', root))) {
		  span = $('span[id]', root);
		  
		  if (conf['Style Buttons']) {
		  	a = $.el('a', {
		  	  className: 'reportbutton',
		  	  innerHTML: '<span class="postblock buttons">!</span>',
		  	  href: 'javascript:;'
		  	});	
		  } else {
		  	a = $.el('a', {
			  className: 'reportbutton',
              innerHTML: '[&nbsp;!&nbsp;]',
		  	  href: 'javascript:;'
		  	});	
		  }
		  
		  $.after(span, a);
		  $.after(span, $.tn(' '));
		}
		return $.on(a, 'click', reportButton.report);
	  });
	},
	report: function() {
	  var id, set, url;
	  url = "http://sys.4chan.org/" + g.BOARD + "/imgboard.php?mode=report&no=" + ($.x('preceding-sibling::input', this).name);
	  id = Date.now();
	  set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200";
	  return window.open(url, id, set);
	}
  };

  threadStats = {
	init: function() {
	  var dialog;
	  dialog = ui.dialog('stats', 'bottom: 0; left: 0;', '<div class=move><span id=postcount>0</span> / <span id=imagecount>0</span></div>');
	  dialog.className = 'dialog';
	  $.add(d.body, dialog);
	  threadStats.posts = threadStats.images = 0;
	  threadStats.imgLimit = (function() {
		switch (g.BOARD) {
		  case 'a':
		  case 'v':
			return 251;
		  default:
			return 151;
		}
	  })();
	  return g.callbacks.push(threadStats.node);
	},
	node: function(root) {
	  var imgcount;
	  if (/\binline\b/.test(root.className)) return;
	  $.id('postcount').textContent = ++threadStats.posts;
	  if (!$('img[md5]', root)) return;
	  imgcount = $.id('imagecount');
	  imgcount.textContent = ++threadStats.images;
	  if (threadStats.images > threadStats.imgLimit) {
		return imgcount.className = 'error';
	  }
	}
  };

  unread = {
	init: function() {
//       d.title = '(0) ' + d.title;
	  $.on(window, 'scroll', unread.scroll);
	  return g.callbacks.push(unread.node);
	},
	replies: [],
	node: function(root) {
	  if (root.hidden || root.className) return;
	  unread.replies.push(root);
	  unread.updateTitle();
//       if (unread.replies.length === 1) return Favicon.update();
	  return Favicon.update();
	},
	scroll: function() {
	  var bottom, height, i, reply, _len, _ref;
	  updater.focus = true;
	  height = d.body.clientHeight;
	  _ref = unread.replies;
	  for (i = 0, _len = _ref.length; i < _len; i++) {
		reply = _ref[i];
		bottom = reply.getBoundingClientRect().bottom;
		if (bottom > height) break;
	  }
	  if (i === 0) return;
	  unread.replies = unread.replies.slice(i);
	  unread.updateTitle();
//       if (unread.replies.length === 0) return Favicon.update();
	  return Favicon.update();
	},
	updateTitle: function() {
//       return d.title = d.title.replace(/\d+/, unread.replies.length);	
	}
  };

  Favicon = {
	init: function() {
	  var favicon, href;
	  favicon = $('link[rel="shortcut icon"]', d.head);
	  favicon.type = 'image/x-icon';
	  href = favicon.href;
	  this.SFW = /ws.ico$/.test(href);
	  this["default"] = href;
	  return this["switch"]();
	},
	"switch": function() {
	    /*
	  switch (conf['favicon']) {
		case 'ferongr':
		  this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
		  this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
		  this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==';
		  break;
		case 'xat-':
		  this.unreadDead = 'data:unreadDead;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC';
		  this.unreadSFW = 'data:unreadSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg==';
		  this.unreadNSFW = 'data:unreadNSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII=';
		  break;
		case 'Mayhem':
		  this.unreadDead = 'data:unreadDead;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg==';
		  this.unreadSFW = 'data:unreadSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC';
		  this.unreadNSFW = 'data:unreadNSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII=';
		  break;
		case 'Original':
		  this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
		  this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
		  this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs=';
		  break;
		case 'None':
		  this.unreadDead = this.dead;
		  this.unreadSFW = 'http://static.4chan.org/image/favicon-ws.ico';
		  this.unreadNSFW = 'http://static.4chan.org/image/favicon.ico';
	  }      
	  */
	  
	  
	  this.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAACB0RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgTVi7kSokAAAAFnRFWHRDcmVhdGlvbiBUaW1lADAyLzAyLzEy1F5urwAAAMBJREFUOMvF0bFKA1EQheGv3H47m23sttgqlQi+gilsJARSCKJYWCQg2ETwtY+Fu3Bh7yagiMVhmDtn/hnmCpBJKZNCM08i+a4khWqQqmcCLBqcqS1tcA6wuMGpO1TrJWBKfqJfNc8AOOB5ZuIGVycBeA+v4QH3xft1WIUBfRWAz/AWXsIOdwVgFfpwGTp0NcBHOISnsMW6AAxj80Vo0dYAx7DHY9jgtgD04+Q2NGiWbrAf46ZyxG6Mzd9+478AvgA4ahrw48vS3gAAAABJRU5ErkJggg==';
	  this.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAACB0RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgTVi7kSokAAAAFnRFWHRDcmVhdGlvbiBUaW1lADAyLzAyLzEy1F5urwAAAMBJREFUOMtj+P//PwMQ/IdhEB8bxqUGLKE3/TAcYzMEnxrCCghYgKGAkAE4XYAvHPDJM+AKNGIxdQ0AgnIgzsHiBQcgtsZrABDUAgOoCIjTgOxoJHFboJgpEOsD2dpYDQCCNqCCKiDOA+IkID8MyQCQZm0gVgFieSBfHpsBzUDJciDOBuJ4ID8IyQB9qGYpIBYG8oWxGdAElCwD0plAOhZIByIZoA21WRhIcwBpDlxhUAalY7EEojyU5qBtNA6IAQBWm2ImdEyROAAAAABJRU5ErkJggg==';
	  this.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAACB0RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgTVi7kSokAAAAFnRFWHRDcmVhdGlvbiBUaW1lADAyLzAyLzEy1F5urwAAAMlJREFUOMtjSDtjzAAE/2EYxMeG0dX8//8fjBmgAnCMzRBsauAG4FKATzNeFxAyAKcL8IUDNnkUA2AccjBFmjEMAIJyIM7BUMTA4ADE1ngNAIJaoHeKgDgNyI5GErcFipkCsT6QrY3VACBoAyqoAuI8IE4C8sOQDABp1gZiFSCWB/LlsRnQDJQsB+JsII4H8oOQDNCHapYCYmEgXxibAU1AyTIgnQmkY4F0IJIB2lCbhYE0B5DmwBUGZVA6FksgykNpDtpG44AYAAA63jTWs5Jf2gAAAABJRU5ErkJggg==';
	  
	  
	  return this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW;
	},
	empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
	dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==',
	update: function() {
	  var clone, favicon, l;
	  l = unread.replies.length;
	  favicon = $('link[rel="shortcut icon"]', d.head);
	  
	  var basefavicon = g.dead ? l ? this.unreadDead : this.dead : l ? this.unread : this["default"];
	  
	  /* thanks to http://remysharp.com/2010/08/24/dynamic-favicons/ */
	  var canvas = document.createElement('canvas');
	  var ctx;
	  var img = document.createElement('img');
	  var link = favicon.cloneNode(true);
	  
	  if (canvas.getContext && unread.replies.length != 0) {
		  canvas.height = canvas.width = 16; // set the size
		  ctx = canvas.getContext('2d');
		  img.onload = function () { // once the image has loaded
		    var text = unread.replies.length;
		    if (text != 0) {
			    ctx.drawImage(this, 0, 0);
			    /*
				ctx.textAlign = 'left';
				ctx.textBaseline = 'top';
			    ctx.font = 'bold 10px "arial", "helvetica", sans-serif';
			    ctx.fillStyle = 'orange';
			    ctx.fillText(g.BOARD, 0, -1);
			    */
			    
			    ctx.font = 'bold 10px "arial", "helvetica", sans-serif';
			    ctx.fillStyle = '#dd0000';
				ctx.textAlign = 'right';
				ctx.textBaseline = 'bottom';
			    ctx.fillText(text, 16, 16, 16); // text, x, y, [optional] maxWidth
			    
			    

			    
			    favicon.href = canvas.toDataURL('image/png');
			    link.href = canvas.toDataURL('image/png');
		    	document.body.appendChild(link);
			} 
		  };
// 		  img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAACB0RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgTVi7kSokAAAAFnRFWHRDcmVhdGlvbiBUaW1lADAyLzAyLzEy1F5urwAAAMBJREFUOMtj+P//PwMQ/IdhEB8bxqUGLKE3/TAcYzMEnxrCCghYgKGAkAE4XYAvHPDJM+AKNGIxdQ0AgnIgzsHiBQcgtsZrABDUAgOoCIjTgOxoJHFboJgpEOsD2dpYDQCCNqCCKiDOA+IkID8MyQCQZm0gVgFieSBfHpsBzUDJciDOBuJ4ID8IyQB9qGYpIBYG8oWxGdAElCwD0plAOhZIByIZoA21WRhIcwBpDlxhUAalY7EEojyU5qBtNA6IAQBWm2ImdEyROAAAAABJRU5ErkJggg==';
		  img.src = basefavicon;
		} else {
			favicon.href = basefavicon;
			if (engine !== 'webkit') {
		        clone = favicon.cloneNode(true);
		        favicon.href = null;
		        return $.replace(favicon, clone);
		      }
		}
	  
//       favicon.href = canvas.toDataURL('image/png');
	  
//       favicon.href = g.dead ? l ? this.unreadDead : this.dead : l ? this.unread : this["default"];

	  /*
	  if (engine !== 'webkit') {
		clone = favicon.cloneNode(true);
		favicon.href = null;
		return $.replace(favicon, clone);
	  }
	  */
	  
	}
  };

  redirect = {
	init: function() {
	  var url;
	  url = location.hostname === 'images.4chan.org' ? redirect.image(g.BOARD, location.pathname.split('/')[3]) : /^\d+$/.test(g.THREAD_ID) ? redirect.thread() : void 0;
	  if (url) return location.href = url;
	},
	image: function(board, filename) {
	  switch (board) {
		case 'a':
		case 'jp':
		case 'm':
		case 'tg':
		case 'tv':
		case 'u':
		  return "http://archive.foolz.us/" + board + "/full_image/" + filename;
	  }
	},
	thread: function() {
	  switch (g.BOARD) {
		case 'a':
		case 'jp':
		case 'm':
		case 'tg':
		case 'tv':
		case 'u':
		  return "http://archive.foolz.us/" + g.BOARD + "/thread/" + g.THREAD_ID + "/";
		case 'lit':
		  return "http://fuuka.warosu.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
		case 'diy':
		case 'g':
		case 'sci':
		  return "http://archive.installgentoo.net/" + g.BOARD + "/thread/" + g.THREAD_ID;
		case '3':
		case 'adv':
		case 'an':
		case 'ck':
		case 'co':
		case 'fa':
		case 'fit':
		case 'int':
		case 'k':
		case 'mu':
		case 'n':
		case 'o':
		case 'p':
		case 'po':
		case 'pol':
		case 'r9k':
		case 'soc':
		case 'sp':
		case 'toy':
		case 'trv':
		case 'v':
		case 'vp':
		case 'x':
		  return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID;
		default:
		  return "http://boards.4chan.org/" + g.BOARD;
	  }
	}
  };

  imgHover = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var thumb;
		if (!(thumb = $('img[md5]', root))) return;
		$.on(thumb, 'mouseover', imgHover.mouseover);
		$.on(thumb, 'mousemove', ui.hover);
		return $.on(thumb, 'mouseout', ui.hoverend);
	  });
	},
	mouseover: function() {
	  ui.el = $.el('img', {
		id: 'ihover',
		src: this.parentNode.href
	  });
	  return $.add(d.body, ui.el);
	}
  };
  
  imgPreloading = {
    init: function() {
      var controls, form, label;
      if (!(controls = $.id('imgControls'))) {
        controls = $.el('div', {
          id: 'imgControls'
        });
        form = $('body > form');
        $.prepend(form, controls);
      }
      label = $.el('label', {
        innerHTML: 'Preload Images<input type=checkbox id=imagePreload>'
      });
      $.on($('input', label), 'click', imgPreloading.click);
      $.add(controls, label);
      return g.callbacks.push(imgPreloading.node);
    },
    click: function() {
      var thumb, _i, _len, _ref, _results;
      if (imgPreloading.on = this.checked) {
        _ref = $$('img[md5]:last-child');
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          thumb = _ref[_i];
          _results.push(imgPreloading.preload(thumb));
        }
        return _results;
      }
    },
    node: function(root) {
      var thumb;
      if (!(imgPreloading.on && (thumb = $('img[md5]:last-child', root)))) return;
      return imgPreloading.preload(thumb);
    },
    preload: function(thumb) {
      return $.el('img', {
        src: thumb.parentNode.href
      });
    }
  };
  
  imgGif = {
	init: function() {
	  return g.callbacks.push(function(root) {
		var src, thumb;
		if (root.hidden || !(thumb = $('img[md5]', root))) return;
		src = thumb.parentNode.href;
		if (/gif$/.test(src)) return thumb.src = src;
	  });
	}
  };

  imgExpand = {
	init: function() {
	  g.callbacks.push(imgExpand.node);
	  return imgExpand.dialog();
	},
	node: function(root) {
	  var a, thumb;
	  if (!(thumb = $('img[md5]', root))) return;
	  a = thumb.parentNode;
	  $.on(a, 'click', imgExpand.cb.toggle);
	  if (imgExpand.on && !root.hidden && root.className !== 'inline') {
		return imgExpand.expand(a.firstChild);
	  }
	},
	cb: {
	  toggle: function(e) {
		if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
		  return;
		}
		e.preventDefault();
		return imgExpand.toggle(this);
	  },
	  all: function() {
		var thumb, _i, _j, _len, _len2, _ref, _ref2, _results, _results2;
		imgExpand.on = this.checked;
		if (imgExpand.on) {
		  _ref = $$('.op > a > img[md5]:last-child, table:not([hidden]) img[md5]:last-child');
		  _results = [];
		  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
			thumb = _ref[_i];
			_results.push(imgExpand.expand(thumb));
		  }
		  return _results;
		} else {
		  _ref2 = $$('img[md5][hidden]');
		  _results2 = [];
		  for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
			thumb = _ref2[_j];
			_results2.push(imgExpand.contract(thumb));
		  }
		  return _results2;
		}
	  },
	  typeChange: function() {
		var klass;
		switch (this.value) {
		  case 'full':
			klass = '';
			break;
		  case 'fit width':
			klass = 'fitwidth';
			break;
		  case 'fit height':
			klass = 'fitheight';
			break;
		  case 'fit screen':
			klass = 'fitwidth fitheight';
		}
		$('body > form').className = klass;
		if (/\bfitheight\b/.test(klass)) {
		  $.on(window, 'resize', imgExpand.resize);
		  if (!imgExpand.style) imgExpand.style = $.addStyle('');
		  return imgExpand.resize();
		} else if (imgExpand.style) {
		  return $.off(window, 'resize', imgExpand.resize);
		}
	  }
	},
	toggle: function(a) {
	  var rect, thumb;
	  thumb = a.firstChild;
	  if (thumb.hidden) {
		rect = a.parentNode.getBoundingClientRect();
		if (rect.top < 0) d.body.scrollTop += rect.top;
		if (rect.left < 0) d.body.scrollLeft += rect.left;
		return imgExpand.contract(thumb);
	  } else {
		return imgExpand.expand(thumb);
	  }
	},
	contract: function(thumb) {
	  thumb.hidden = false;
	  return $.rm(thumb.nextSibling);
	},
	expand: function(thumb, url) {
	  var a, filesize, img, max;
	  if (thumb.hidden) return;
	  a = thumb.parentNode;
	  img = $.el('img', {
		src: url || a.href
	  });
	  if (engine === 'gecko' && a.parentNode.className !== 'op') {
		filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent;
		max = filesize.match(/(\d+)x/);
		img.style.maxWidth = "" + max[1] + "px";
	  }
	  if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error);
	  thumb.hidden = true;
	  return $.add(a, img);
	},
	error: function() {
	  var href, req, src, thumb, url;
	  href = this.parentNode.href;
	  thumb = this.previousSibling;
	  imgExpand.contract(thumb);
	  src = href.split('/');
	  if (this.src.split('/')[2] === 'images.4chan.org' && (url = redirect.image(src[3], src[5]))) {
		setTimeout(imgExpand.expand, 10000, thumb, url);
		return;
	  }
	  url = href + '?' + Date.now();
	  if (engine === 'webkit') {
		return req = $.ajax(this.src, (function() {
		  if (this.status !== 404) {
			return setTimeout(imgExpand.expand, 10000, thumb, url);
		  }
		}), {
		  type: 'head',
		  event: 'onreadystatechange'
		});
	  } else if (!g.dead) {
		return setTimeout(imgExpand.expand, 10000, thumb, url);
	  }
	},
	dialog: function() {
	  var controls, form, imageType, select;
	  controls = $.el('div', {
		id: 'imgControls',
		innerHTML: "<select id=imageType name=imageType><option value=full>Full</option><option value='fit width'>Fit Width</option><option value='fit height'>Fit Height</option value='fit screen'><option value='fit screen'>Fit Screen</option></select><label>Expand Images<input type=checkbox id=imageExpand></label>"
	  });
	  imageType = $.get('imageType', 'full');
	  select = $('select', controls);
	  select.value = imageType;
	  imgExpand.cb.typeChange.call(select);
	  $.on(select, 'change', $.cb.value);
	  $.on(select, 'change', imgExpand.cb.typeChange);
	  $.on($('input', controls), 'click', imgExpand.cb.all);
	  form = $('body > form');
	  return $.prepend(form, controls);
	},
	resize: function() {
	  return imgExpand.style.innerHTML = ".fitheight img[md5] + img {max-height:" + d.body.clientHeight + "px;}";
	}
  };

  Main = {
	init: function() {
	  var cutoff, hiddenThreads, id, now, pathname, temp, timestamp, _ref;
	  pathname = location.pathname.slice(1).split('/');
	  g.BOARD = pathname[0], temp = pathname[1];
	  if (temp === 'res') {
		g.REPLY = true;
		g.THREAD_ID = pathname[2];
	  } else {
		g.PAGENUM = parseInt(temp) || 0;
	  }
	  if (location.hostname === 'sys.4chan.org') {
		$.ready(qr.sys);
		return;
	  }
	  if (location.hostname === 'images.4chan.org') {
		$.ready(function() {
		  if (d.title === '4chan - 404') return redirect.init();
		});
		return;
	  }
	  $.ready(options.init);
	  $.on(window, 'message', Main.message);
	  now = Date.now();
	  /*
	  if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) {
		$.ready(function() {
		  return $.add(d.head, $.el('script', {
//             src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js'
			src: 'http://userscripts.org/scripts/source/125646.meta.js'
		  }));
		});
		$.set('lastUpdate', now);
	  }
	  */
	  g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {});
	  if ($.get('lastChecked', 0) < now - 1 * DAY) {
		$.set('lastChecked', now);
		cutoff = now - 7 * DAY;
		hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {});
		for (id in hiddenThreads) {
		  timestamp = hiddenThreads[id];
		  if (timestamp < cutoff) delete hiddenThreads[id];
		}
		_ref = g.hiddenReplies;
		for (id in _ref) {
		  timestamp = _ref[id];
		  if (timestamp < cutoff) delete g.hiddenReplies[id];
		}
		$.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads);
		$.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
	  }
	  if (conf['Filter']) filter.init();
	  if (conf['Reply Hiding']) replyHiding.init();
	  if (conf['Filter'] || conf['Reply Hiding']) strikethroughQuotes.init();
	  if (conf['Anonymize']) anonymize.init();
	  if (conf['Time Formatting']) Time.init();
	  if (conf['Sauce']) sauce.init();
	  if (conf['Image Auto-Gif']) imgGif.init();
	  if (conf['Image Hover']) imgHover.init();
	  if (conf['Reveal Spoilers']) revealSpoilers.init();
	  if (conf['Report Button']) reportButton.init();
	  if (conf['Quote Inline']) quoteInline.init();
	  if (conf['Quote Preview']) quotePreview.init();
	  if (conf['Quote Backlinks']) quoteBacklink.init();
	  if (conf['Indicate OP quote']) quoteOP.init();
	  if (conf['Indicate Cross-thread Quotes']) quoteDR.init();
	  return $.ready(Main.ready);
	},
	ready: function() {
	  var callback, canPost, form, node, nodes, _i, _j, _len, _len2, _ref;
	  if (conf['404 Redirect'] && d.title === '4chan - 404') {
		redirect.init();
		return;
	  }
	  if (!$.id('navtopr')) return;
	  $.addClass(d.body, engine);
	  $.addStyle(Main.css);
	  threading.init();
	  Favicon.init();
	  if ((form = $('form[name=post]')) && (canPost = !!$.id('recaptcha_response_field'))) {
		Recaptcha.init();
		if (g.REPLY && conf['Auto Watch Reply'] && conf['Thread Watcher']) {
		  $.on(form, 'submit', function() {
			if ($('img.favicon').src === Favicon.empty) {
			  return watcher.watch(null, g.THREAD_ID);
			}
		  });
		}
	  }
	  if (conf['Auto Noko'] && canPost) form.action += '?noko';
	  if (conf['Cooldown'] && canPost) cooldown.init();
	  if (conf['Image Expansion']) imgExpand.init();
	  if (conf['Quick Reply'] && canPost) qr.init();
	  if (conf['Thread Watcher']) watcher.init();
	  if (conf['Keybinds']) keybinds.init();
	  if (g.REPLY) {
		if (conf['Thread Updater']) updater.init();
		if (conf['Image Preloading']) imgPreloading.init();
		if (conf['Thread Stats']) threadStats.init();
		if (conf['Reply Navigation']) nav.init();
		if (conf['Post in Title']) titlePost.init();
		if (conf['Unread Count']) unread.init();
		if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) {
		  qr.dialog();
		  if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true;
		}
	  } else {
		if (conf['Thread Hiding']) threadHiding.init();
		if (conf['Thread Expansion']) expandThread.init();
		if (conf['Comment Expansion']) expandComment.init();
		if (conf['Index Navigation']) nav.init();
		if (conf['Top on Refresh']) nav.top();		
	  }
	  nodes = $$('.op, a + table');
	  _ref = g.callbacks;
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		callback = _ref[_i];
		try {
		  for (_j = 0, _len2 = nodes.length; _j < _len2; _j++) {
			node = nodes[_j];
			callback(node);
		  }
		} catch (err) {
		  alert(err);
		}
	  }
	  return $.on($('form[name=delform]'), 'DOMNodeInserted', Main.node);
	},
	message: function(e) {
	  var data, origin;
	  origin = e.origin, data = e.data;
	  if (origin === 'http://sys.4chan.org') {
		return qr.message(data);
	  } else if (data.version && data.version !== VERSION && confirm('An updated version of 4chan W is available, would you like to install it now?')) {
//         return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + data.version + "/4chan_x.user.js";
		  return window.location = "http://userscripts.org/scripts/source/125646.user.js";
	  }
	},
	node: function(e) {
	  var callback, target, _i, _len, _ref, _results;
	  target = e.target;
	  if (target.nodeName !== 'TABLE') return;
	  _ref = g.callbacks;
	  _results = [];
	  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
		callback = _ref[_i];
		try {
		  _results.push(callback(target));
		} catch (err) {

		}
	  }
	  return _results;
	},
	css: '\
	  /* dialog styling */\
	  .dialog {\
		border: 1px solid rgba(0,0,0,.25);\
	  }\
	  .move {\
		cursor: move;\
	  }\
	  label, .favicon, #qr_form > div > img {\
		cursor: pointer;\
	  }\
	  a[href="javascript:;"] {\
		text-decoration: none;\
	  }\
\
	  .thread.stub > :not(.block),\
	  #content > [name=tab]:not(:checked) + div,\
	  #updater:not(:hover) > :not(.move),\
	  #qp > input, #qp .inline, .forwarded {\
		display: none;\
	  }\
\
	  .new {\
		background: lime;\
	  }\
	  .error {\
		color: red;\
	  }\
	  #error {\
		cursor: default;\
	  }\
	  #error[href] {\
		cursor: pointer;\
	  }\
	  td.replyhider {\
		vertical-align: top;\
	  }\
\
	  .filesize + br + a {\
		float: left;\
		pointer-events: none;\
	  }\
	  img[md5], img[md5] + img {\
		pointer-events: all;\
	  }\
	  .fitwidth img[md5] + img {\
		max-width: 100%;\
	  }\
	  .gecko  > .fitwidth img[md5] + img,\
	  .presto > .fitwidth img[md5] + img {\
		width: 100%;\
	  }\
\
	  #qp, #ihover {\
		position: fixed;\
	  }\
\
	  #ihover {\
		max-height: 100%;\
		max-width: 75%;\
		padding-bottom: 18px;\
	  }\
\
	  #navlinks {\
		font-size: 16px;\
		position: fixed;\
		top: 25px;\
		right: 5px;\
	  }\
\
	  #overlay {\
		position: fixed;\
		top: 0;\
		right: 0;\
		left: 0;\
		bottom: 0;\
		text-align: center;\
		background: rgba(0,0,0,.5);\
		z-index: 1;\
	  }\
	  #overlay::after {\
		content: "";\
		display: inline-block;\
		height: 100%;\
		vertical-align: middle;\
	  }\
	  #options {\
		display: inline-block;\
		padding: 5px;\
		text-align: left;\
		vertical-align: middle;\
		width: 500px;\
	  }\
	  #credits {\
		float: right;\
	  }\
	  #options ul {\
		list-style: none;\
		padding: 0;\
	  }\
	  #options label {\
		text-decoration: underline;\
	  }\
	  #content > div {\
		height: 450px;\
		overflow: auto;\
	  }\
	  #content textarea {\
		margin: 0;\
		min-height: 100px;\
		resize: vertical;\
		width: 100%;\
	  }\
	  #flavors {\
		height: 100%;\
	  }\
\
	  #qr {\
		position: fixed;\
		max-height: 100%;\
		overflow-x: hidden;\
		overflow-y: auto;\
	  }\
	  #qr > div.move {\
		text-align: right;\
	  }\
	  #qr input[name=name] {\
		float: left;\
	  }\
	  #qr_form {\
		clear: left;\
	  }\
	  #qr_form, #qr #com_submit, #qr input[name=upfile] {\
		margin: 0;\
	  }\
	  #qr textarea {\
		width: 100%;\
		height: 125px;\
	  }\
	  #qr #close, #qr #autohide {\
		float: right;\
	  }\
	  #qr:not(:hover) > #autohide:checked ~ .autohide {\
		height: 0;\
		overflow: hidden;\
	  }\
	  /* http://stackoverflow.com/questions/2610497/change-an-inputs-html5-placeholder-color-with-css */\
	  #qr input::-webkit-input-placeholder {\
		color: grey;\
	  }\
	  #qr input:-moz-placeholder {\
		color: grey;\
	  }\
	  /* qr reCAPTCHA */\
	  #qr img {\
		border: 1px solid #AAA;\
	  }\
\
	  #updater {\
		position: fixed;\
		text-align: right;\
	  }\
	  #updater input[type=text] {\
		width: 50px;\
	  }\
	  #updater:not(:hover) {\
		border: none;\
		background: transparent;\
	  }\
\
	  #stats {\
		border: none;\
		position: fixed;\
	  }\
\
	  #watcher {\
		position: absolute;\
	  }\
	  #watcher > div {\
		overflow: hidden;\
		padding-right: 5px;\
		padding-left: 5px;\
		text-overflow: ellipsis;\
		max-width: 200px;\
		white-space: nowrap;\
	  }\
	  #watcher > div.move {\
		text-decoration: underline;\
		padding-top: 5px;\
	  }\
	  #watcher > div:last-child {\
		padding-bottom: 5px;\
	  }\
\
	  #qp {\
		padding-bottom: 5px;\
	  }\
	  .qphl {\
		outline: 2px solid rgba(216, 94, 49, .7);\
	  }\
	  .inlined {\
		opacity: .5;\
	  }\
	  .inline td.reply {\
		background-color: rgba(255, 255, 255, 0.15);\
		border: 1px solid rgba(128, 128, 128, 0.5);\
	  }\
	  .filetitle, .replytitle, .postername, .commentpostername, .postertrip {\
		background: none;\
	  }\
	  .filtered {\
		text-decoration: line-through;\
	  }\
\
	  #files > input {\
		display: block;\
	  }\
\
	  .buttons {\
		 font-size: 8pt;\
		 border-radius: 3px;\
		 opacity: 0.5;\
		 line-height: normal;\
		 position: relative;\
	  }\
\
	  span.buttons {\
	  	 padding-left: 5px;\
	  	 padding-right: 5px;\
		 top: -2px;\
	}\
\
	  a.buttons {\
		 margin-right: 4px;\
		 letter-spacing: -1pt;\
		 padding-left: 4px;\
		 padding-right: 4px;\
	}\
\
	  .replyhide {\
		 opacity: 0.2;\
	  }\
\
	  .buttons:hover {\
		 opacity: 1;\
	  }\
\
	  a.buttons:hover {\
		 color: inherit;\
	  }\
	'
  };

  Main.init();

}).call(this);